Task Cancellation
Task
andTask<TResult>
support cancellation through the use of cancellation token.- In the Task classes, cancellation involves cooperation between the user delegate, which represents a cancelable operation, and the code that requested the cancellation.
- A successful cancellation involves the requesting code calling the
Cancel
method onCancellationTokenSource
object and the user delegate terminating the operation in a timely manner by returning from the delegate or throwingThrowIfCancellationRequested
On Cancel throw exception
- Create an object for
CancellationTokenSource
- Get the token object from
Token
property of token source - Pass the token object to the
Task.Run
method - Call
Cancel
method on token source object to raise a cancel request. - In the task delegate check for
IsCancellationRequested
and terminate the operation
Task status is set to Cancelled
state when ThrowIfCancellationRequested
method is called.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Run(() => {
while(true) {
token.ThrowIfCancellationRequested();
if(token.IsCancellationRequested)
{
// add cleanup code
token.ThrowIfCancellationRequested();
}
}
}, token);
Thread.Sleep(2000);
tokenSource.Cancel();
try
{
task.Wait();
}
catch(AggregateException e)
{
Console.WriteLine("Task Status: {0}, Error Messaage: {1}", task.Status, e.InnerException.Message);
}
finally
{
tokenSource.Dispose();
}
On Cancel return
Task status is set to RanToCompletion
when the code simply returns.
var tasks = new List<Task<int>>();
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
int completedIterations = 0;
for(int i=0; i<10; i++) {
tasks.Add(Task.Run(() => {
int x=0;
for(x =0; x<200000; x++)
{
if(token.IsCancellationRequested)
return x;
}
Interlocked.Increment(ref completedIterations);
if(completedIterations > 5 && !tokenSource.IsCancellationRequested) {
tokenSource.Cancel();
}
return x;
}, token));
}
try
{
Task.WaitAll(tasks.ToArray());
}
catch(AggregateException)
{
Console.WriteLine("{0, 10} {1,20} {2, 14}", "Task Id", "Status", "Iterations");
foreach(var t in tasks)
{
Console.WriteLine("{0, 10} {1,20} {2, 14:N0}", t.Id, t.Status, t.Status != TaskStatus.Canceled ? t.Result.ToString(): "Not Started");
}
}
}
Cancel a Task and its Children
private static async Task CancelTaskAndChildren()
{
var tasks = new ConcurrentBag<Task>();
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Task t1 = Task.Run(() => DoSomeWork(1, token), token);
Console.WriteLine($"Task {t1.Id} executing");
tasks.Add(t1);
Task t2 = Task.Run(() => {
Task t;
for(int i=3; i<=10; i++) {
t = Task.Factory.StartNew((obj) => DoSomeWork((int)obj, token), i, token);
Console.WriteLine($"Task {t.Id} executing");
tasks.Add(t);
}
DoSomeWork(2, token);
}, token);
Console.WriteLine($"Task {t2.Id} executing");
tasks.Add(t2);
Task.Delay(100).Wait();
tokenSource.Cancel();
Console.WriteLine($"Tasks cancellation requested");
try
{
await Task.WhenAll(tasks.ToArray());
}
catch(OperationCanceledException e)
{
Console.WriteLine(nameof(OperationCanceledException));
}
finally
{
tokenSource.Dispose();
}
foreach(var task in tasks)
Console.WriteLine($"Task {task.Id} {task.Status}");
}
private static void DoSomeWork(int taskNum, CancellationToken ct)
{
if(ct.IsCancellationRequested)
{
Console.WriteLine($"Task {taskNum} was cancelled before it got started");
ct.ThrowIfCancellationRequested();
}
var sw = new SpinWait();
for(int i=0; i<100; i++)
{
sw.SpinOnce();
if(ct.IsCancellationRequested)
{
Console.WriteLine($"Task {taskNum} cancelled");
ct.ThrowIfCancellationRequested();
}
}
Console.WriteLine($"Task {taskNum} completed");
}