Summary#
Make a CancellationTokenSource, hand its Token to the work you start, and call Cancel() when you no longer want
it. A task that was handed the token stops — even if it never reads the token itself — and awaiting it raises
TaskCanceledException, which catch (OperationCanceledException) takes.
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); // cancels itself after five seconds
var work = Fetch(url, cts.Token);
try { return await work; }
catch (OperationCanceledException) { return "gave up"; }Signature#
new CancellationTokenSource() // cancelled when you say so
new CancellationTokenSource(TimeSpan delay) // …or by itself after delay (milliseconds work too)
CancellationTokenSource.CreateLinkedTokenSource(a, b) // cancelled when any of the tokens is
cts.Token · cts.Cancel() · cts.CancelAfter(delay) · cts.IsCancellationRequested · cts.Dispose()
CancellationToken.None // a token nothing can cancel
token.IsCancellationRequested · token.CanBeCanceled · token.ThrowIfCancellationRequested()
new TaskCompletionSource<T>() // a task you finish yourself (no <T>: a Task with no result)
tcs.Task // the ONE task it finishes
tcs.SetResult(v) · tcs.SetException(ex) · tcs.SetCanceled() // a second one raises InvalidOperationException
tcs.TrySetResult(v) · tcs.TrySetException(ex) · tcs.TrySetCanceled() // …these answer false insteadDescription#
What stops when a token is cancelled?#
A task that was handed the token. A method started as a task with a token among its arguments stops when the token is cancelled: its own waits end, and so do the outbound calls it is making. It does not have to check the token for that to happen.
Task.Delay(delay, token)ends at once withTaskCanceledException.Code that checks.
token.ThrowIfCancellationRequested()raisesOperationCanceledExceptiononce the token is cancelled;token.IsCancellationRequestedreads it.A call awaited in place.
await Work(url, token)— a method handed the token as an argument stops too: its waits and its outbound calls end, and theawaitraisesTaskCanceledException, though it never read the token itself.Task.Run(() => …, token)with the token already cancelled never starts the work: the task is cancelled. Once started, it stops when the token is.A
Parallelloop givennew ParallelOptions { CancellationToken = token }starts no body once it is cancelled and raisesOperationCanceledException(Do the same work for many items at once (Parallel, SemaphoreSlim)).
Which exceptions does cancelling raise?#
OperationCanceledException, and TaskCanceledException, which derives from it — so catch (OperationCanceledException)
takes both, as in C#. Awaiting a task that was cancelled raises TaskCanceledException.
When does a TaskCompletionSource help?#
When a task's answer comes from somewhere other than the method that made it — one task finishes it, another waits
for it. tcs.Task is the same task every time you read it. Awaiting it before anything can finish it would wait
forever in C#; here, when nothing still running could finish it, the await raises InvalidOperationException
saying so.
Can a class hold a token or a source?#
Yes — private CancellationTokenSource? stop;, public SemaphoreSlim Gate = new SemaphoreSlim(3); and a
CancellationToken field are ordinary class fields, held for as long as the object lives (never stored in a row).
Can a source or a completion source cross a pause?#
A token can — it is carried as whether it was cancelled. A CancellationTokenSource, a TaskCompletionSource and a
SemaphoreSlim hold timers and waiting tasks, which cannot be written into a pause (a call into the browser, a
workflow wait), so holding one across it is refused by name: make it after the pause instead.
Examples#
async Task<int> Answer(int n, CancellationToken token) {
await Task.Delay(TimeSpan.FromMilliseconds(n * 100), token);
return n;
}
async Task<int> Fastest() {
var cts = new CancellationTokenSource();
var first = await Task.WhenAny(Answer(1, cts.Token), Answer(5, cts.Token), Answer(9, cts.Token));
cts.Cancel(); // the other two stop now, instead of running to their end
return await first;
}int CountUntilCancelled(CancellationToken token) {
var n = 0;
while (n < 1000) {
token.ThrowIfCancellationRequested();
n++;
}
return n;
}
string Run() {
var cts = new CancellationTokenSource();
cts.Cancel();
try { return $"{CountUntilCancelled(cts.Token)}"; }
catch (OperationCanceledException) { return "stopped"; }
}async Task Produce(TaskCompletionSource<string> answer) {
await Task.Delay(TimeSpan.FromMilliseconds(20));
answer.SetResult("ready");
}
async Task<string> Consume() {
var answer = new TaskCompletionSource<string>();
var producing = Produce(answer);
var value = await answer.Task;
await producing;
return value;
}See also#
- async / await and Task — starting a task, and what a task may do
- Wait for several tasks (Task.WhenAll, Task.WhenAny) — racing tasks, and what happens to the losers
- Wait in place (Task.Delay) — a wait that a token can end
- Do the same work for many items at once (Parallel, SemaphoreSlim) —
Parallel.ForEachAsynchands each body a token