Here we’ll look at a very basic example of a cancellable Task
. This is an addition to the post Synchronous to asynchronous in .NET. This is a complete runnable code demo of a simple Console
application that runs a slow process asynchronously. User has an option to “Press C” to cancel the operation.
We’ll first have the actual synchronous method CancellableWork()
that is invoked asynchronously. It accepts a CancellationToken
and periodically checks if cancellation is requested on the token. If requested, it aborts the operation and throws a TaskCanceledException
.
The method CancellableTask()
starts the operation asynchronously and passes a cancellation token. It then retunes the resulting Task
to the calling method. So, the calling method can pass a CancellationToken
while invoking this method and request a cancellation later.
Note: Just passing a CancellationToken
and requesting a Cancel()
doesn’t do much by itself. It’s up to the actual working logic to check and respond to IsCancellationRequested
. Also, the code decides whether to throw a TaskCanceledException
by convention, or not.
private void CancellableWork(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Cancelled work before start");
cancellationToken.ThrowIfCancellationRequested();
}
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine($"Cancelled on iteration # {i + 1}");
//the following lien alone is enough to check and throw
cancellationToken.ThrowIfCancellationRequested();
}
Console.WriteLine($"Iteration # {i + 1} completed");
}
}
public Task CancellableTask(CancellationToken ct)
{
return Task.Factory.StartNew(() => CancellableWork(ct), ct);
}
Now we look at the Main()
method of the application that invokes our CancellableTask()
method with a cancellation token. If user presses C
, cancellation is requested to the method.
Then we Wait()
for the Task to complete. We also do try-catch
to look for any exception. If we get a TaskCanceledException
in the AggregateException
thrown by the task, we know (by convention only), that the task was aborted because of the requested cancellation.
static void Main(string[] args)
{
Console.WriteLine("Starting application...");
CancellationTokenSource source = new CancellationTokenSource();
//assuming the wrapping class is TplTest
var task = new TplTest().CancellableTask(source.Token);
Console.WriteLine("Heavy process invoked");
Console.WriteLine("Press C to cancel");
Console.WriteLine("");
char ch = Console.ReadKey().KeyChar;
if (ch == 'c' || ch == 'C')
{
source.Cancel();
Console.WriteLine("\nTask cancellation requested.");
}
try
{
task.Wait();
}
catch (AggregateException ae)
{
if (ae.InnerExceptions.Any(e => e is TaskCanceledException))
Console.WriteLine("Task cancelled exception detected");
else
throw;
}
catch (Exception)
{
throw;
}
finally
{
source.Dispose();
}
Console.WriteLine("Process completed");
Console.ReadKey();
}
This program generates the following output.
Starting application...
Heavy process invoked
Press C to cancel
Iteration # 1 completed
Iteration # 2 completed
Iteration # 3 completed
c
Task cancellation requested.
Cancelled on iteration # 4
Task cancelled exception detected
Process completed
All posts in the series - Tasks, Threads, Asynchronous
- Synchronous to asynchronous in .NET
- Basic task cancellation demo in C#
- How does Async-Await work - Part I
- How does Async-Await work - Part II
- Multithreading - lock, Monitor & Mutex | Thread synchronization Part I
- Multithreading with non-exclusive locks | Thread synchronization Part II
- Multithreading with signals | Thread synchronization Part III
- Non-blocking multithreading & concurrent collections | Thread synchronization Part IV