Oaraisi
Oaraisi

Reputation: 55

New Task.WaitAsync(CancellationToken) API and exceptions

I'm trying to use the new .NET 6 Task.WaitAsync(CancellationToken) API. What I'd like to accomplish is to cancel the waiting for a task, while still being capable of trying to cancel the task itself (it calls an async library and I cannot be sure it will observe the cancellationToken I passed in, or at least not in a timely manner) and avoid to swallow any possible exception it could throw.

So, for example, let's say I want to call an async method:

private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
    //do something before the call

    await Library.DoSomethingAsync(cancellationToken); // Let's pretend
        // this is a call to a libary that accepts a cancellationToken,
        // but I cannot be 100% sure it will be observed
}

from a button click event handler:

private async void button1_Click(object sender, EventArgs e)
{
    var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
    var tsk = DoSomethingAsync(cts.Token);
    try
    {
        await tsk.WaitAsync(cts.Token);
    }
    catch (Exception ex) when (ex is OperationCanceledException)
    {
        tsk.Forget();
    }
}

Now I'm sure the await will last 5 seconds max, but when the OperationCanceledException is caught the task could still be running and I don't want to swallow any of the exceptions that it could throw. So what can I do now if I don't want to await it?

I thought using a FireAndForget extension method like this inside the catch block:

public static async void Forget(this Task task)
{
    try
    {
        await task.ConfigureAwait(false);
    }
    catch (Exception)
    {
        throw;
    }
}

Is this an acceptable pattern, or should I just trust the library and hope it will sooner or later be canceled anyway? And what if it will never do so, will the Forget method await forever?

Upvotes: 1

Views: 2701

Answers (1)

Theodor Zoulias
Theodor Zoulias

Reputation: 43941

You could combine the WaitAsync and Forget functionality in a single extension method like the one below:

public async static Task WaitAsyncAndThenOnErrorCrash(this Task task,
    CancellationToken cancellationToken)
{
    Task waitTask = task.WaitAsync(cancellationToken);
    try { await waitTask; }
    catch when (waitTask.IsCanceled) { OnErrorCrash(task); throw; }

    static async void OnErrorCrash(Task task)
    {
        try { await task.ConfigureAwait(false); }
        catch when (task.IsCanceled) { } // Ignore overdue cancellation
    }
}

Usage example:

private async void button1_Click(object sender, EventArgs e)
{
    using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
    try
    {
        await DoSomethingAsync(cts.Token).WaitAsyncAndThenOnErrorCrash(cts.Token);
    }
    catch (OperationCanceledException) { } // Ignore
}

In case the DoSomethingAsync completes with error either before or after the cancellation, the application will crash with a popup saying "Unhandled exception has occurred in your application". The user will have the option to continue running the app, by clicking the "Continue" button:

Screenshot

Upvotes: 2

Related Questions