I want to cancel a task started by one class from some other class. Below is an example program where i have two classes and want to cancel the task from different class. Cancellation token doesn't seem to work.
namespace threadingiwthcancel
{
class ThreadExample
{
public async Task PrintSomething(CancellationToken token)
{
int i = 0;
while (true)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancel requested");
break;
}
await Task.Delay(TimeSpan.FromSeconds(1));
Console.WriteLine(i);
i++;
}
}
}
class Cancel
{
public void cancelTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
}
}
class Program
{
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel();
c.cancelTask();
Console.ReadLine();
}
private static async void callasyncmethod(CancellationToken token)
{
ThreadExample te = new ThreadExample();
await te.PrintSomething(token);
}
}
}
Cancellation token doesnt seem to work.
The reason for failure is that you are creating a different CancellationTokenSource and calling Cancel on it. Sadly, no other operations will be notified and so your original task continues.
I want to cancel a task started by one class from some other class
Be sure to pass the original source to it:
class Cancel
{
CancellationTokenSource _source;
public Cancel (CancellationTokenSource source)
{
_source = source;
}
public void cancelTask()
{
_source.Cancel();
}
}
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel(source); // pass in the original source
c.cancelTask();
Console.ReadLine();
}
if you want to do like that than you need to pass object of CancellationSoruce to you cancel method in other class , do not create new CancellationSoruce
class Cancel
{
public void cancelTask(CancellationTokenSource source )
{
source.Cancel();
}
}
in main
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel();
c.cancelTask(source );
Console.ReadLine();
}
or can do like this
class Cancel
{
private readonly CancellationTokenSource source;
public Cancel(CancellationTokenSource source)
{
this.source = soruce;
}
public void cancelTask()
{
source.Cancel();
}
}
and in main
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
callasyncmethod(token);
Thread.Sleep(TimeSpan.FromSeconds(2));
Cancel c = new Cancel(source );
c.cancelTask();
Console.ReadLine();
}
Related
If I call Stop(), OperationCanceledException is happened and _writer.TryComplete(exp) is true. But _reader.Completion Task is still not completed.
Is it desired behavior of Channels? If yes can someone tell me how to stop a Channel without waiting till it's empty and have its Completion Task in Completed state?
public interface IItem
{
Uri SourceUri { get; }
string TargetPath { get; }
}
public class Item : IItem
{
public Item(Uri sourceUri, string targetPath)
{
SourceUri = sourceUri;
TargetPath = targetPath;
}
public Uri SourceUri { get; }
public string TargetPath { get; }
}
public class TestService
{
private readonly ChannelWriter<IItem> _writer;
private readonly ChannelReader<IItem> _reader;
private readonly CancellationTokenSource _cts;
public TestService()
{
_cts = new CancellationTokenSource();
Channel<IItem> channel = Channel.CreateUnbounded<IItem>();
_reader = channel.Reader;
_writer = channel.Writer;
}
public async Task QueueDownload(IItem information)
{
await _writer.WriteAsync(information);
}
public void StartDownload()
{
Task.Factory.StartNew(async () =>
{
await ProcessDownloadAsync();
}, TaskCreationOptions.LongRunning);
}
public void Stop()
{
_cts.Cancel();
//_writer.Complete();
//_writer = null;
Console.WriteLine("Stop");
}
public async Task Wait()
{
await _reader.Completion;
}
private async Task ProcessDownloadAsync()
{
try
{
while (await _reader.WaitToReadAsync(_cts.Token))
{
IItem information = await _reader.ReadAsync(_cts.Token);
using (WebClient webClient = new WebClient())
{
Console.WriteLine(information.TargetPath);
await webClient.DownloadFileTaskAsync(information.SourceUri,
information.TargetPath);
}
}
}
catch (OperationCanceledException exp)
{
bool res = _writer.TryComplete(exp);
}
}
}
static class Program
{
static async Task Main(string[] args)
{
TestService tSvc = new TestService();
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
await tSvc.QueueDownload(new Item(new Uri(#"https://images.pexels.com/" +
#"photos/753626/pexels-photo-753626.jpeg"), #"D:\\Temp\1.png"));
tSvc.StartDownload();
Task t = tSvc.Wait();
tSvc.Stop();
await t;
Console.WriteLine("Finished");
}
}
The ChannelWriter.Complete method behaves a bit differently than one would expect. It is not invalidating instantly the contents of the channel. Instead, it just prevents adding more items in the channel. The existing items are still valid for consumption, and the ChannelReader.Completion property will not complete before all stored items are consumed.
The example below demonstrates this behavior:
var channel = Channel.CreateUnbounded<int>();
channel.Writer.TryWrite(1);
channel.Writer.Complete(new FileNotFoundException());
//channel.Reader.TryRead(out var data);
var completed = channel.Reader.Completion.Wait(500);
Console.WriteLine($"Completion: {(completed ? "OK" : "Timed-out")}");
Output:
Completion: Timed-out
You can uncomment the channel.Reader.TryRead line, to see the FileNotFoundException to emerge.
What I need to do is able to cancel a task that is running async. Task should be cancelled on cancel button click. I have done it with CancellationTokenSource. But it is not working properly.
public class classA
{
CancellationTokenSource _tokenSource = null;
public void OnCancelButtonClick()
{
MessageBox.Show("Do you Really want to cancel upload");
_tokenSource = new CancellationTokenSource();
_tokenSource.Cancel();
}
public async void UploadBtnClick(object param )
{
_tokenSource = new CancellationTokenSource();
var token = _tokenSource.Token;
try
{
await Task.Run(() => UploadFunction(token));
}
catch(OperationCanceledException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
_tokenSource.Dispose();
}
}
public Task<bool> UploadFunction(CancellationToken token)
{
foreach
{
//code here
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
}
}
}
I am calling OnCancelButtonClick from another function
public class classB
{
public void CancelUploadBtnExecute(object param)
{
FilesViewModel vm = new FilesViewModel();
vm.OnCancelButtonClick();
}
}
when I click on OnCancelButtonClick , IsCancellationRequested is false, but not reflected inside UploadFunction so the task won't stop.
It's because you are initializing new CancellationTokenSource in OnCancelButtonClick().
Just remove this line from OnCancelButtonClick()
_tokenSource = new CancellationTokenSource();
UploadFunction method is using token created from CancellationTokenSource in UploadBtnClick method.
I need to spawn multiple tasks having in mind that I could add more tasks through a SignalR hub later on. Those tasks should be named, so I can search for a specific task. Each of these tasks should be able to be canceled independently (separate CancellationTokenSource).
Questions:
Is there a different collection that I can use?
Can I improve my code in any way?
public interface IMyClass
{
void Start(string name);
void Stop(string name);
}
public class MyClass : IMyClass
{
private List<Tuple<string, Task, CancellationTokenSource>> _tasks = new List<Tuple<string, Task, CancellationTokenSource>>();
public void Start(string name)
{
CancellationTokenSource cts = new CancellationTokenSource();
Task task = Task.Factory.StartNew(() => DoWork(name, cts.Token));
_tasks.Add(new Tuple<string, Task, CancellationTokenSource>(name, task, cts));
}
public void Stop(string name)
{
foreach (var tuple in _tasks)
{
if (tuple.Item1.Contains(name))
{
CancellationTokenSource cts = tuple.Item3;
cts.Cancel();
}
}
}
public void DoWork(string name, CancellationToken token)
{
try
{
while (true)
{
Console.WriteLine($"{name} is working");
// long operation...
Thread.Sleep(1000);
if (token.IsCancellationRequested)
{
Console.WriteLine($"{name} canceled");
token.ThrowIfCancellationRequested();
}
}
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
}
}
I think it looks fine, but you may consider if it is allowed to use the same task name twice.
And instead of
private List<Tuple<string, Task, CancellationTokenSource>> _tasks = new List<Tuple<string, Task, CancellationTokenSource>>();
you could use a dictionary
private Dictionary<string,Tuple<Task, CancellationTokenSource>> _tasks = new Dictionary<string,Tuple<Task, CancellationTokenSource>>();
I would personally wrap the information about the task in a new type, that makes it easier to maintain and add new features later. Something like this:
public class TaskInfo
{
public string Name { get; set; }
public Task Task { get; set; }
public CancellationTokenSource Token { get; set; }
}
// Define other methods and classes here
public interface IMyClass
{
void Start(string name);
void Stop(string name);
}
public class MyClass : IMyClass
{
private Dictionary<string,TaskInfo> _tasks = new Dictionary<string,TaskInfo>();
public void Start(string name)
{
if(_tasks.ContainsKey(name))
throw new Exception($"Task with name {name} already exists");
CancellationTokenSource cts = new CancellationTokenSource();
TaskInfo taskInfo = new TaskInfo() {
Token = cts,
Name = name,
Task = Task.Factory.StartNew(() => DoWork(name, cts.Token))
};
_tasks.Add(name,taskInfo);
}
public void Stop(string name)
{
if (_tasks.ContainsKey(name)) {
_tasks[name].Token.Cancel();
}
}
public void DoWork(string name, CancellationToken token)
{
try
{
while (true)
{
Console.WriteLine($"{name} is working");
// long operation...
Thread.Sleep(1000);
if (token.IsCancellationRequested)
{
Console.WriteLine($"{name} canceled");
token.ThrowIfCancellationRequested();
}
}
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
}
}
this may not be a better solution, but i prefer an approach like this
I need to execute a kind of LongRunning task after a delay.
Each Task can be cancelled. I prefer TPL with cancellationToken.
Since my task is long running and before starting a task it has to be placed in dictionary I have to use new Task(). But I've faced different behavior - when task is created using new Task() after Cancel() it throws TaskCanceledException whereas a task created with Task.Run doesn't throw an exception.
Generally I need to recognize the difference and not get TaskCanceledException.
It's my code:
internal sealed class Worker : IDisposable
{
private readonly IDictionary<Guid, (Task task, CancellationTokenSource cts)> _tasks =
new Dictionary<Guid, (Task task, CancellationTokenSource cts)>();
public void ExecuteAfter(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
var cts = new CancellationTokenSource();
var task = new Task(async () =>
{
await Task.Delay(waitBeforeExecute, cts.Token);
action();
}, cts.Token, TaskCreationOptions.LongRunning);
cancellationId = Guid.NewGuid();
_tasks.Add(cancellationId, (task, cts));
task.Start(TaskScheduler.Default);
}
public void ExecuteAfter2(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
var cts = new CancellationTokenSource();
cancellationId = Guid.NewGuid();
_tasks.Add(cancellationId, (Task.Run(async () =>
{
await Task.Delay(waitBeforeExecute, cts.Token);
action();
}, cts.Token), cts));
}
public void Abort(Guid cancellationId)
{
if (_tasks.TryGetValue(cancellationId, out var value))
{
value.cts.Cancel();
//value.task.Wait();
_tasks.Remove(cancellationId);
Dispose(value.cts);
Dispose(value.task);
}
}
public void Dispose()
{
if (_tasks.Count > 0)
{
foreach (var t in _tasks)
{
Dispose(t.Value.cts);
Dispose(t.Value.task);
}
_tasks.Clear();
}
}
private static void Dispose(IDisposable obj)
{
if (obj == null)
{
return;
}
try
{
obj.Dispose();
}
catch (Exception ex)
{
//Log.Exception(ex);
}
}
}
internal class Program
{
private static void Main(string[] args)
{
Action act = () => Console.WriteLine("......");
Console.WriteLine("Started");
using (var w = new Worker())
{
w.ExecuteAfter(act, TimeSpan.FromMilliseconds(10000), out var id);
//w.ExecuteAfter2(act, TimeSpan.FromMilliseconds(10000), out var id);
Thread.Sleep(3000);
w.Abort(id);
}
Console.WriteLine("Enter to exit");
Console.ReadKey();
}
}
UPD:
This approach also works without exception
public void ExecuteAfter3(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
var cts = new CancellationTokenSource();
cancellationId = Guid.NewGuid();
_tasks.Add(cancellationId, (Task.Factory.StartNew(async () =>
{
await Task.Delay(waitBeforeExecute, cts.Token);
action();
}, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default), cts)); ;
}
The reason of the inconsistent behavior is fundamentally incorrect usage of an async delegate in the first case. The Task constructors just don't receive Func<Task> and your asynchronous delegate is always interpreted as async void not async Task in case of using with constructor. If an exception is raised in an async Task method it's caught and placed into Task object which isn't true for an async void method, in that case exception just bubbles up out of the method to a synchronization context and goes under category of unhandled exceptions (you can familiarize with details in this Stephen Cleary article). So what happens in case of using constructor: a task which is supposed to initiate asynchronous flow is created and started. Once it reaches point when Task.Delay(...) returns a promise, the task completes and it has no more relationship to anything which happens in Task.Delay continuation (you can easily check in debugger by setting a breakpoint to value.cts.Cancel() that the task object in the _tasks dictionary has status RanToCompletetion while however the task delegate essentially is still running). When a cancellation is requested the exception is raised inside the Task.Delay method and without existence of any promise object is being promoted to app domain.
In case of Task.Run the situation is different because there are overloads of this method which are able to accept Func<Task> or Func<Task<T>> and unwrap the tasks internally in order to return underlying promise instead of wrapped task which ensures proper task object inside the _tasks dictionary and proper error handling.
The third scenario despite the fact that it doesn't throw an exception it is partially correct. Unlike Task.Run, Task.Factory.StartNew doesn't unwrap underlying task to return promise, so task stored in the _tasks is just wrapper task, like in the case with constructor (again you can check its state with debugger). It however is able to understand Func<Task> parameters, so asynchronous delegate has async Task signature which allows at least to handle and store exception in the underlying task. In order to get this underlying task with Task.Factory.StartNew you need to unwrap the task by yourself with Unwrap() extension method.
The Task.Factory.StartNew isn't considered as a beast practice of creating tasks because of certain dangers related to its application (see there). It however can be used with some caveats if you need to apply specific options like LongRunning which cannot be directly applied with Task.Run.
I don't know why I got down votes here but it's inspired me to update my answer.
UPDATED
My full approach:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp4
{
internal class Program
{
private static void Main(string[] args)
{
using (var delayedWorker = new DelayedWorker())
{
delayedWorker.ProcessWithDelay(() => { Console.WriteLine("100"); }, TimeSpan.FromSeconds(5), out var cancellationId_1);
delayedWorker.ProcessWithDelay(() => { Console.WriteLine("200"); }, TimeSpan.FromSeconds(10), out var cancellationId_2);
delayedWorker.ProcessWithDelay(() => { Console.WriteLine("300"); }, TimeSpan.FromSeconds(15), out var cancellationId_3);
Cancel_3(delayedWorker, cancellationId_3);
Console.ReadKey();
}
}
private static void Cancel_3(DelayedWorker delayedWorker, Guid cancellationId_3)
{
Task.Run(() => { delayedWorker.Abort(cancellationId_3); }).Wait();
}
internal sealed class DelayedWorker : IDisposable
{
private readonly object _locker = new object();
private readonly object _disposeLocker = new object();
private readonly IDictionary<Guid, (Task task, CancellationTokenSource cts)> _tasks = new Dictionary<Guid, (Task task, CancellationTokenSource cts)>();
private bool _disposing;
public void ProcessWithDelay(Action action, TimeSpan waitBeforeExecute, out Guid cancellationId)
{
Console.WriteLine("Creating delayed action...");
CancellationTokenSource tempCts = null;
CancellationTokenSource cts = null;
try
{
var id = cancellationId = Guid.NewGuid();
tempCts = new CancellationTokenSource();
cts = tempCts;
var task = new Task(() => { Process(action, waitBeforeExecute, cts); }, TaskCreationOptions.LongRunning);
_tasks.Add(cancellationId, (task, cts));
tempCts = null;
task.ContinueWith(t =>
{
lock (_disposeLocker)
{
if (!_disposing)
{
TryRemove(id);
}
}
}, TaskContinuationOptions.ExecuteSynchronously);
Console.WriteLine($"Created(cancellationId: {cancellationId})");
task.Start(TaskScheduler.Default);
}
finally
{
if (tempCts != null)
{
tempCts.Dispose();
}
}
}
private void Process(Action action, TimeSpan waitBeforeExecute, CancellationTokenSource cts)
{
Console.WriteLine("Starting delayed action...");
cts.Token.WaitHandle.WaitOne(waitBeforeExecute);
if (cts.Token.IsCancellationRequested)
{
return;
}
lock (_locker)
{
Console.WriteLine("Performing action...");
action();
}
}
public bool Abort(Guid cancellationId)
{
Console.WriteLine($"Aborting(cancellationId: {cancellationId})...");
lock (_locker)
{
if (_tasks.TryGetValue(cancellationId, out var value))
{
if (value.task.IsCompleted)
{
Console.WriteLine("too late");
return false;
}
value.cts.Cancel();
value.task.Wait();
Console.WriteLine("Aborted");
return true;
}
Console.WriteLine("Either too late or wrong cancellation id");
return true;
}
}
private void TryRemove(Guid id)
{
if (_tasks.TryGetValue(id, out var value))
{
Remove(id, value.task, value.cts);
}
}
private void Remove(Guid id, Task task, CancellationTokenSource cts)
{
_tasks.Remove(id);
Dispose(cts);
Dispose(task);
}
public void Dispose()
{
lock (_disposeLocker)
{
_disposing = true;
}
if (_tasks.Count > 0)
{
foreach (var t in _tasks)
{
t.Value.cts.Cancel();
t.Value.task.Wait();
Dispose(t.Value.cts);
Dispose(t.Value.task);
}
_tasks.Clear();
}
}
private static void Dispose(IDisposable obj)
{
if (obj == null)
{
return;
}
try
{
obj.Dispose();
}
catch (Exception ex)
{
//log ex
}
}
}
}
}
I'm running into the issue where the task being executed below runs asynchronously but is unable to be cancelled when the serializer reads the memory stream. When the user makes the cancellation request (by pressing a cancel button), a cancellation (the method cancel() is called fromt he token) is made but the task continues.
Service Class:
Async method called from LoadHelper() in Main class
public async void StartTask(Action callback, CancellationToken token)
{
await Task.Run(callback, token);
}
Main Class:
private void LoadHelper()
{
_services.GetInstance<IThreadService>().StartTask(
() => LoadHelperAsync(), _cancelService.Token);
}
Method being run async
private void LoadHelperAsync()
{
var serializer = new DataContractSerializer(typeof(UserDatabase));
string selectedDir;
ComparisonResult = null;
_services.GetInstance<IOpenFileService>().DisplayOpenFileDialog(
"Select a saved comparison",
"Comparison File(*.xml; *.cmp)|*.xml;*.cmp",
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
out selectedDir);
if (!string.IsNullOrEmpty(selectedDir))
{
_dispatchService.BeginInvoke(() => IsExecuting = true);
using (FileStream fileStream = new FileStream(selectedDir, FileMode.Open, FileAccess.Read))
using (DeflateStream compressedStream = new DeflateStream(fileStream, CompressionMode.Decompress))
using (BufferedStream regularStream = new BufferedStream(fileStream))
{
Stream memoryStream;
//Use filename to determine compression
if (selectedDir.EndsWith(".cmp", true, null))
{
memoryStream = compressedStream;
}
else
{
memoryStream = regularStream;
}
Report("Loading comparison");
Report(0);
IsExecuting = true;
ComparisonResult = (UserDatabase)serializer.ReadObject(memoryStream);
memoryStream.Close();
fileStream.Close();
IsExecuting = false;
Report("Comparison loaded");
}
_dispatchService.BeginInvoke(() => IsExecuting = false);
_dispatchService.BeginInvoke(() => ViewResults.ExecuteIfAble());
}
else
{
Report("No comparison loaded");
}
Cancellation code:
This command is binded to a "cancel" button in the view.
CancelCompare = new Command(o => _cancelService.Cancel(), o => IsExecuting);
From the CancellationService class
public class CancellationService : ICancellationService, IDisposable
{
private CancellationTokenSource _tokenSource;
public CancellationService()
{
Reset();
}
public CancellationToken Token { get; private set; }
public void Cancel()
{
_tokenSource.Cancel();
}
public void Reset()
{
_tokenSource = new CancellationTokenSource();
Token = _tokenSource.Token;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
Calling _tokenSource.Cancel(); won't do anything to a running task. A cancellation token interrupts task execution only if it was canceled before Task.Run
Take a look:
static void Main(string[] args)
{
using (var tokenSource = new CancellationTokenSource())
{
var aTask = StartTask(() =>
{
while (true)
{
Console.WriteLine("Nothing is going to happen.");
// Some long operation
Thread.Sleep(1000);
}
}, tokenSource.Token);
tokenSource.Cancel();
aTask.Wait();
}
Console.ReadKey();
}
static async Task StartTask(Action callback, CancellationToken cancellationToken)
{
await Task.Run(callback, cancellationToken);
}
If you want to cancel the execution, you should check the cancellation token by yourself, there is no any magic which cancels the task for you:
static void Main(string[] args)
{
using (var tokenSource = new CancellationTokenSource())
{
var aTask = StartTask(cancellationToken =>
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine("Will stop before that if canceled.");
// Some long operation
// Also pass the token all way down
Task.Delay(1000, cancellationToken).Wait();
}
}, tokenSource.Token);
// Try 0, 500, 1500 to see the difference
Thread.Sleep(1500);
tokenSource.Cancel();
try
{
aTask.Wait();
}
catch (Exception ex)
{
// AggregateException with OperationCanceledException
Console.WriteLine("Task was canceled.");
Console.WriteLine(ex.ToString());
}
}
Console.ReadKey();
}
static async Task StartTask(Action<CancellationToken> callback, CancellationToken cancellationToken)
{
await Task.Run(() => callback(cancellationToken), cancellationToken);
}
Note that this code only illustrates how the task works, never write anything like this in production.