【问题标题】:Associate a CancellationToken with an async method's Task将 CancellationToken 与异步方法的任务相关联
【发布时间】:2013-02-21 20:02:03
【问题描述】:

问题:有没有办法将CancellationToken 与从async 方法返回的Task 关联起来?

通常,如果OperationCancelledExceptionCancellationToken 匹配TaskCancellationToken 被抛出,Task 将最终处于Canceled 状态。如果它们不匹配,则任务进入Faulted 状态:

void WrongCancellationTokenCausesFault()
{
    var cts1 = new CancellationTokenSource();
    var cts2 = new CancellationTokenSource();
    cts2.Cancel();

    // This task will end up in the Faulted state due to the task's CancellationToken
    // not matching the thrown OperationCanceledException's token.
    var task = Task.Run(() => cts2.Token.ThrowIfCancellationRequested(), cts1.Token);  
}

对于async/await,我还没有找到一种方法来设置方法的TaskCancellationToken(从而实现相同的功能)。从我的测试来看,anyOperationCancelledException 似乎会导致async 方法进入Canceled 状态:

async Task AsyncMethodWithCancellation(CancellationToken ct)
{
    // If ct is cancelled, this will cause the returned Task to be in the Cancelled state
    ct.ThrowIfCancellationRequested(); 
    await Task.Delay(1);

    // This will cause the returned Task to be in the Cancelled state
    var newCts = new CancellationTokenSource();
    newCts.Cancel();
    newCts.Token.ThrowIfCancellationRequested();
}

如果我从async 方法调用的方法被取消(而且我不希望取消——即不是这个TaskCancellationToken ),我希望任务进入Faulted 状态——而不是Canceled 状态。

【问题讨论】:

    标签: c# async-await task-parallel-library cancellation-token


    【解决方案1】:

    我认为该设计适用于常见情况:如果取消任何子操作,则取消传播到父操作(最常见的情况是父子操作共享取消令牌)。

    如果你想要不同的语义,你可以在你的 async 方法中 catch OperationCanceledException 并抛出一个符合你需要的语义的异常。如果你想重复使用这些语义,Task 的扩展方法应该适合。

    【讨论】:

    • 我同意——它适用于常见情况。一个孩子Task(非async)可能最终出现故障(由于OperationCancelledException带有错误的令牌),这有点奇怪,但这会导致父(async)任务进入已取消状态。大多数情况下,我只是对错误案例感到好奇和担心:意外取消不会导致我的 async 代码失败,并且可能会延迟我注意到错误。
    • 有一些特殊情况重新取消;例如,如果你将一个已经取消的令牌传递给Task.Run,你将得到一个取消的任务,当awaited 时引发TaskCanceledException,但如果你在任务开始后取消传递给Task.Run 的令牌,你'将得到一个取消的任务,当awaited 时引发OperationCanceledExceptionnot TaskCanceledException)。无论如何,您不必担心错误,因为您最终应该 await 所有任务(您关心的),并且任何取消的任务都会在 awaited 时抛出 OperationCanceledException
    【解决方案2】:

    这是一个Run 方法,当提供CancellationToken 参数时,它试图模仿Task.Run 方法的行为。 Run 返回的任务只有在异步方法返回 Canceled 任务并且关联的 CancellationToken 与提供的参数匹配时才能变为 Canceled

    /// <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();
    }
    

    使用示例:

    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);
    

    输出:

    Faulted
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-07
      • 1970-01-01
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 2018-07-01
      相关资源
      最近更新 更多