Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to chain async tasks with a CancellationToken?

I'd like to chain some tasks but conditionally continue with the execution if the CancellationToken I have wasn't fired.

What I'm aiming to achieve is something equivalent to

var cts = new CancellationTokenSource();
var cancellationToken = cts.Token;
var t = Task.Run(async () => {
    if (cancellationToken.IsCancellationRequested) return;
    await t1();
    if (cancellationToken.IsCancellationRequested) return;
    await t2();
    if (cancellationToken.IsCancellationRequested) return;
    await t3();
    if (cancellationToken.IsCancellationRequested) return;
    await t4();
});
var timeout = Task.Delay(TimeSpan.FromSeconds(4));
var completedTask = await Task.WhenAny(t, timeout);
if (completedTask != t)
{
    cts.Cancel();
    await t;
}

That's what I have by now and it is working, though it is also verbose.

like image 427
Tiago Dall'Oca Avatar asked Dec 12 '25 02:12

Tiago Dall'Oca


2 Answers

var cts = new CancellationTokenSource();
var t = Task.Run(async () => {
     await t1();
     await t2();
     await t3();
     await t4();
 }, cts.Token);

cts.CancelAfter(TimeSpan.FromSeconds(4));

try
{
     await t;
}
catch (OperationCanceledException)
{
     // The cancellation token was triggered, add logic for that
     ....
}
like image 178
Claudiu Guiman Avatar answered Dec 13 '25 14:12

Claudiu Guiman


Your original code is correct-ish -- it assumes that you always want the individual tasks to run to completion and that if cancelled you want the overall task to complete with success. Neither of these things are idiomatic.

A more normal way to do it would be something like:

var cts = new CancellationTokenSource();
var cancellationToken = cts.Token;
var t = Task.Run(async () => {
    cancellationToken.ThrowIfCancellationRequested();
    await t1(cancellationToken);
    cancellationToken.ThrowIfCancellationRequested();
    await t2(cancellationToken);
    cancellationToken.ThrowIfCancellationRequested();
    await t3(cancellationToken);
    cancellationToken.ThrowIfCancellationRequested();
    await t4(cancellationToken);
}, cancellationToken);

Then, somewhere else:

cts.Cancel();

You can omit the calls to ThrowIfCancellationRequested here assuming that the individual tasks check it fairly soon after entry, but the core idea is that you should be passing the token down to the innermost loop of whatever is doing the work, and they should throw on cancellation by calling that, which ends up setting the task to a cancelled state rather than a success state.

(So really you only need to actually call ThrowIfCancellationRequested if you hit a function that doesn't accept a CancellationToken parameter -- which is why all async methods should do so, as otherwise its task will be non-cancellable.)

like image 40
Miral Avatar answered Dec 13 '25 15:12

Miral