Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

Stop a task (CancellationToken) and complete one by hand (TaskCompletionSource)

var cts = new CancellationTokenSource(); Work(cts.Token); cts.Cancel(); · token.ThrowIfCancellationRequested() · var tcs = new TaskCompletionSource<T>(); tcs.SetResult(v); await tcs.Task;

C#'s cancellation, with C#'s meaning. A CancellationTokenSource hands out a Token; cancel the source and every task that was handed the token stops — its waits and its outbound calls with it — and awaiting it raises TaskCanceledException. A TaskCompletionSource<T> is a task you finish yourself, with SetResult, SetException or SetCanceled.

stable3 examples compiled by CIfunctionconcurrencystdlib

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 instead

Description#

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 with TaskCanceledException.

  • Code that checks. token.ThrowIfCancellationRequested() raises OperationCanceledException once the token is cancelled; token.IsCancellationRequested reads 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 the await raises TaskCanceledException, 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 Parallel loop given new ParallelOptions { CancellationToken = token } starts no body once it is cancelled and raises OperationCanceledException (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#

Related

async / await and Task

C#'s async and await, with C#'s meaning. A call to a Task-returning method that you do not await where you make it…

Wait for several tasks (Task.WhenAll, Task.WhenAny)

Task.WhenAll waits for every task and answers their results in order; Task.WhenAny answers the first task to finish…

Wait in place (Task.Delay)

Wait before carrying on — between checks on a job another system is still running, before trying a call again, or for a…

Do the same work for many items at once (Parallel, SemaphoreSlim)

Parallel.For, Parallel.ForEach and Parallel.ForEachAsync run one body per item, each as a task beside the others, and…