Home > front end >  New Task.WaitAsync(CancellationToken) api and exceptions
New Task.WaitAsync(CancellationToken) api and exceptions

Time:12-03

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 secs 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?

CodePudding user response:

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) { one rrorCrash(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

CodePudding user response:

If you are waiting on a task that may or may not honor a cancellation token, yet want to respect a timeout and move on while capturing any errors from the task, then your approach is valid (among others)... Assuming, all you want to do is log the error of the original task.

Note : Throwing in your Forget method (as in your example) may not have your desired effect, as the task becomes unobserved. However if all you want to do is log or otherwise handle that exception, then your approach is likely fine.

Consider the following output

Given

public static async Task Forget(Task task)
{
   try
   {
      await task.ConfigureAwait(false);
   }
   catch (Exception ex)
   {
      Console.WriteLine(ex.Message);
      // throw; // futile
   }
}

private static async Task DoSomethingAsync(CancellationToken ctsToken)
{
   // ignore the token
   await Task.Delay(5000);
   Console.WriteLine("Finished");
   throw new Exception("Test");
}

Example

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
var tsk = DoSomethingAsync(cts.Token);
try
{
   await Forget(tsk).WaitAsync(cts.Token);
}
catch (Exception ex) when (ex is OperationCanceledException)
{
   Console.WriteLine("canceled");
}

Console.ReadKey();

Output

Canceled
Finished
Test
  • Related