How to chain async tasks with a CancellationToken?

c# cancel task without cancellationtoken
c# task.run cancellationtoken
task continuewith example
continuewith async
cancel async task c#
cancellationtokensource
c# chain tasks
c# kill task

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.

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
     ....
}

Chaining Tasks by Using Continuation Tasks, A continuation task is an asynchronous task that's invoked by Cancel a continuation either before it starts or cooperatively as it is running. Cancel a list of tasks You can extend the previous example to cancel many tasks by associating the same CancellationTokenSource instance with each task. If you choose the Cancel button, you cancel all tasks that aren’t yet complete.

It seems like your goal is to just stop execution if the whole operation has taken longer than 4 seconds.

If you were passing the CancellationToken to your t1/t2/etc. methods, I'd say you couldn't do any better than what you have. But as you have it, you could just use a Stopwatch instead of a CancellationToken:

var timeout = TimeSpan.FromSeconds(4);
var stopwatch = new Stopwatch();
stopwatch.Start();

await t1();
if (stopwatch.Elapsed > timeout) return;
await t2();
if (stopwatch.Elapsed > timeout) return;
await t3();
if (stopwatch.Elapsed > timeout) return;
await t4();
stopwatch.Stop();

I assume this is in a method somewhere, where you can use return, but you can modify if needed (return a value, throw an exception, etc.).

Cancel an Async Task or a List of Tasks (C#), You can set up a button that you can use to cancel an async application if you don't want to wait for it to finish. By following the examples in this  Cancel asynchronous operations in C#. Running asynchronous code is pretty easy with .NET and C#. As we sometimes need to cancel an ongoing asynchronous operation we will see, throughout this post, how to cancel tasks using CancellationToken, even for non-cancellable tasks.

Your code is functionally OK, but when reading it at a glance it's not clear what it's doing. So I suggest that you encapsulate this logic inside a utility method with descriptive name and parameters:

public static async Task RunSequentially(IEnumerable<Func<Task>> taskFactories,
    int timeout = Timeout.Infinite, bool onTimeoutAwaitIncompleteTask = false)
{
    using (var cts = new CancellationTokenSource(timeout))
    {
        if (onTimeoutAwaitIncompleteTask)
        {
            await Task.Run(async () =>
            {
                foreach (var taskFactory in taskFactories)
                {
                    if (cts.IsCancellationRequested) throw new TimeoutException();
                    await taskFactory();
                }
            });
        }
        else // On timeout return immediately
        {
            var allSequentially = Task.Run(async () =>
            {
                foreach (var taskFactory in taskFactories)
                {
                    cts.Token.ThrowIfCancellationRequested();
                    var task = taskFactory(); // Synchronous part of task
                    cts.Token.ThrowIfCancellationRequested();
                    await task; // Asynchronous part of task
                }
            }, cts.Token);
            var timeoutTask = new Task(() => {}, cts.Token);
            var completedTask = await Task.WhenAny(allSequentially, timeoutTask);
            if (completedTask.IsCanceled) throw new TimeoutException();
            await completedTask; // Propagate any exception
        }
    }
}

This code defers from yours in that it throws a TimeoutException on time-out. I think that it's better to force the caller to handle explicitly this exception, instead of hiding the fact that the operation timed-out. The caller can ignore the exception by leaving empty the catch block:

try
{
    await RunSequentially(new[] { t1, t2, t3, t4 },
        timeout: 4000,
        onTimeoutAwaitIncompleteTask: true);
}
catch (TimeoutException)
{
    // Do nothing
}

Cancel asynchronous operations in C#, Do you have more precision ? Prateek Singh • 9 months ago. Can we also abort or restart running task from other running task? Whether you’re doing async work or not, accepting a CancellationToken as a parameter to your method is a great pattern for allowing your caller to express lost interest in the result. Supporting cancelable operations comes with a little bit of extra responsibility on your part.

You should consider using Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:

IObservable<Unit> tasks =
    from x1 in Observable.FromAsync(() => t1())
    from x2 in Observable.FromAsync(() => t2())
    from x3 in Observable.FromAsync(() => t3())
    from x4 in Observable.FromAsync(() => t4())
    select Unit.Default;

IObservable<Unit> timer =
    Observable
        .Timer(TimeSpan.FromSeconds(4.0))
        .Select(x => Unit.Default)

IDisposable subscription =
    Observable
        .Amb(tasks, timer)
        .Subscribe();

If the timer observable fires before the tasks are complete then the entire pipeline is canceled. No unnecessary tasks are run.

If you want to cancel manually then just call subscription.Dispose().

The code is simple and beautiful too.

Async/Await easy cancellation in c# - DEV, An easy way to cancel any type of Tasks after a timeout in c#. Tagged with csharp , dotnet, webdev, productivity. Dim t As Task(Of Date) = Task(Of Date).Run(Function() DoWork()) ' Create a chain of continuation tasks, where each task is ' followed by another task that performs work. Dim continuations As New List(Of Task(Of DateTime))() For i As Integer = 0 To 4 ' Provide the current time as the state of the continuation.

Since no one here gave a better answer I'll do it myself.

The code I provided in the question is, in my opinion, most cohesive and also general enough to be recycled in other cases.

Add a way to await a CancellationToken · Issue #14991 · dotnet , public static Task WhenCanceled(this CancellationToken cancellationToken) State associated with the continuation chain will be kept alive  Task.Run(Sub() Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.") Console.WriteLine("Or let the task time out by doing nothing.") If Console.ReadKey(True).KeyChar = "c"c Then cts.Cancel() End If End Sub) ' Let the user read the UI message.

Understanding Cancellation Callbacks, Net asynchronous methods support cancellation tokens: When invoking a method, Net's Task Parallel Library and its CancellationToken and  public static async Task < T > WithCancellation < T >( this Task < T > task, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource < bool >(); using (cancellationToken. Register ( s => (( TaskCompletionSource < bool >) s ).

C# Task Examples (Task.Run, ContinueWith and Wait), Run with ContinueWith and Wait. Specify a CancellationToken. Task. a method at some point in the future, and ContinueWith to add another method after that. async In this way we can chain methods, one after another, on a thread pool. 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.

Async tips and best practices in C#, Should I add a cancellation token parameter? Assuming you are returning a Task rather than IAsyncAction or IAsyncOperation (more on these  As of.NET 4.6 there does not seem to be a way to create a Task from a CancellationToken. This functionality is of course easy to build with the Register method but it should be included in the framework. The signature would be Task WhenCancelled (this CancellationToken ct)

Comments
  • Passing a CancellationToken as parameter of Task.Run has the effect of cancelling the Task only if it is still scheduled and hasn't started yet. So your code will either allow ALL tasks to run, or neither of them. This is not what the OP wants.
  • Sorry pal, this does not do what I want.
  • Using a CancellationToken is a must for me in this case.
  • @TiagoDall'Oca Why? Is this going to be called from other code that needs to pass a CancellationToken?
  • Yes. This is some pattern that we might adopt while developing some functionalities that enable cancellation. Timeout here is really just a specific use case.
  • What you did here does seem to be a bit of an overkill but I liked the idea of putting tasks in a collection.
  • @TiagoDall'Oca I didn't use a collection because each Task is awaited separately, and there is no reason to keep track of completed Tasks. I could have used a collection to hold the results of the Tasks though, if I had to do with Task<TResult>s, to return these results to the caller. In C# 8 not even this is necessary, since I could return an IAsyncEnumerable<TResult>, and sent the results to the caller one by one.
  • Yeah, it looks pretty good to me but I'm really looking for a way to use CancellationToken in my specific case. Very neat code though! Thx
  • @TiagoDall'Oca - Why a CancellationToken? There's nothing about your tasks that you showed in your question that can be cancelled. In any case, it is possible to use Rx with a CancellationToken. I'd need to know more about what you're doing.