繁体   English   中英

TPL 任务继续:任务在 state 中发生故障而不是取消时取消

[英]TPL task continuation: task is in state Faulted instead of Canceled when cancelled

取消以下任务时,该任务不在 state 已取消但出现故障:

    private string ReturnString()
    {
        // throw new OperationCanceledException(_cancellationToken);   // This puts task in faulted, not canceled
        Task.Delay(5000, _cancellationToken).Wait(_cancellationToken); // Simulate work (with IO-bound call)
        // throw new OperationCanceledException(_cancellationToken);   // This puts task in faulted, not canceled
        // _cancellationToken.ThrowIfCancellationRequested();          // This puts task in faulted, not canceled  
        // throw new Exception("Throwing this exception works!");      // This works as expected (faulted)
        return "Ready";
    }

    private void SetReturnValueWithTaskContinuation()
    {
        SynchronizationContext synchronizationContext = SynchronizationContext.Current;
        Task<string> task = Task.Run(() => ReturnString());
        task.ContinueWith(
        antecedent =>
        {
            if (antecedent.Status == TaskStatus.Canceled)
            {
                synchronizationContext.Post(result => _txtResultContinueWith.Text = (string)result, "Cancelled");
            }
            else if (antecedent.Status == TaskStatus.Faulted)
            {
                synchronizationContext.Post(result => _txtResultContinueWith.Text = (string)result, "Exception");
            }
            else
            {
                synchronizationContext.Post(result => _txtResultContinueWith.Text = (string)result, antecedent.Result);
            }
        });
    }

我知道,在抛出 OperationCanceled 异常时必须提供取消令牌。 我知道,有两种方法可以抛出 OperationCanceled 异常,其中 ThrowIfCancellationRequested() 是首选方法。 而且我知道,延续链的取消令牌应该与要取消的任务的取消令牌不同,否则延续链也会被取消。 为了简单起见,我只使用一个取消令牌来取消任务本身。 但是,该任务有 state“故障”而不是“取消”。 那是一个错误吗? 如果不是,那就是 TPL 的可用性问题。 有人可以帮忙吗?

Task.Run确实希望我们为取消状态的正确传播提供取消令牌,请参阅:

CancellationToken.ThrowIfCancellationRequested 之后的故障与取消任务状态

如果我们使用接受ActionTask.RunFunc<T>委托(其中T不是Task )的覆盖,这一点尤其重要。 如果没有令牌,在这种情况下,返回的任务状态将是Faulted而不是Canceled

但是,如果委托类型是Func<Task>Func<Task<T>> (例如, async lambda),则Task.Run会对其进行特殊处理。 委托返回的任务被解包,并且它的取消状态被正确传播。 因此,如果我们像下面这样修改您的ReturnString ,您将按预期获得Canceled状态,并且您不必将令牌传递给Task.Run

private Task<string> ReturnString()
{
    Task.Delay(5000, _cancellationToken).Wait(_cancellationToken);
    return Task.FromResult("Ready");
}

// ...

Task<string> task = Task.Run(() => ReturnString()); // Canceled status gets propagated

如果对Task.Run以这种方式工作的原因感到好奇,您可以深入了解它的实现细节

但请注意,虽然从Task.Run 4.5 中引入 Task.Run 到 .NET Core 3.0 的当前版本,这种行为一直是一致的,但它仍然是未记录的并且是特定于实现的,所以我们不应该依赖它。 例如,使用Task.Factory.StartNew仍然会产生Faulted

Task<string> task = Task.Factory.StartNew(() => ReturnString(),
     CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current).Unwrap();

除非您关联取消令牌,否则Task.Factory.StartNew将在此处返回Canceled任务的唯一时间是ReturnString已被修改为async 不知何故,编译器生成的async state 机器管道改变了ReturnString返回的Task的行为。

通常,最好向Task.RunTask.Factory.StartNew提供取消令牌。 在您的情况下,如果这是不可能的,您可能希望将ReturnString设为async方法:

private async Task<string> ReturnString()
{
    var task = Task.Run(() => 
    {
        Thread.Sleep(1500); // CPU-bound work
        _cancellationToken.ThrowIfCancellationRequested();
    });

    await task; // Faulted status for this task

    // but the task returned to the caller of ReturnString 
    // will be of Canceled status,
    // thanks to the compiler-generated async plumbing magic

    return "Ready";
}

通常, 异步同步不是一个好主意,但ReturnString是一个私有方法,它实现一些 GUI 逻辑以将工作卸载到池线程,这可能是 go 的方式。

现在,如果您想将其从当前同步上下文中删除,您可能只需要用另一个Task.Run包装它(即使您这样做,取消状态仍将正确传播):

Task<string> task = Task.Run(() => ReturnString());

附带说明一下,不担心同步上下文的常见模式是在任何地方常规使用ConfigureAwait

await Task.Run(...).ConfigureAwait(continueOnCapturedContext: false); 

但我自己已经无意识地停止使用ConfigureAwait ,原因如下:

重新访问 Task.ConfigureAwait(continueOnCapturedContext: false)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM