Reputation: 55
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
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:
Upvotes: 2