Here is a Run
method that attempts to imitate the behavior of the Task.Run
method, when supplied with a CancellationToken
argument. The task returned by the Run
can only become Canceled
if the async method returns a Canceled
task, and also the associated CancellationToken
matches the supplied argument.
/// <summary>
/// Invokes an async method (a method implemented with the async keyword), and
/// returns a proxy of the produced async task. In case the async task completes
/// in a Canceled state but the causative CancellationToken is not equal with the
/// cancellationToken argument, the proxy transitions to a Faulted state.
/// In all other cases, the proxy propagates the status of the async task as is.
/// </summary>
public static Task<TResult> Run<TResult>(Func<Task<TResult>> asyncMethod,
CancellationToken cancellationToken)
{
return asyncMethod().ContinueWith(t =>
{
if (t.IsCanceled)
{
try { t.GetAwaiter().GetResult(); }
catch (Exception ex)
{
// In case the async method is canceled with an unknown token, throw
// the exception. The continuation will complete in a Faulted state.
if (ex is OperationCanceledException oce &&
oce.CancellationToken != cancellationToken) throw;
}
}
return t; // In any other case, propagate the task as is.
}, default, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)
.Unwrap();
}
Usage example:
Task<int> task = Run(async () =>
{
await Task.Delay(1000, new CancellationToken(true));
return 13;
}, new CancellationToken(false));
try { task.Wait(); } catch { }
Console.WriteLine(task.Status);
Output:
Faulted