TPL任务继续:任务处于故障状态,而不是被取消时取消

问题描述 投票:1回答:1

取消以下任务时,该任务的状态不是“已取消但有故障:

    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()是首选方式。而且我知道,延续链的取消令牌应该与要取消的任务的取消令牌不同,否则继续链也会被取消。为了简化起见,我仅使用一个取消令牌来取消任务本身。但是,该任务的状态为“ Faulted”而不是“ Canceled”。那是个错误吗?如果不是,那么这就是TPL的可用性问题。有人可以帮忙吗?

c# concurrency task task-parallel-library continuations
1个回答
0
投票

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

Faulted vs Canceled task status after CancellationToken.ThrowIfCancellationRequested

如果我们使用接受Task.RunAction委托的Func<T>替代,其中T不为Task的任何东西,则这尤其重要。如果没有令牌,则返回的任务状态将是Faulted,而不是Canceled

但是,如果委托类型是Func<Task> / Func<Task<T>(例如async lambda),则会得到一些特殊待遇。委托返回的任务将被解包,并且其取消状态将正确传播。因此,如果我们按如下所示更改ReturnString,您将获得预期的Canceled状态,并且不必将令牌传递给Task.Run

private Task<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 Task.FromResult("Ready");
}

// ...

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

[更好的是,如果有可能使ReturnString自然地异步,那可能是最好的选择,甚至可能不需要用Task.Run包装它(但如果这样做,取消传播仍然会工作):

private async Task<string> ReturnString()
{
    // throw new OperationCanceledException(_cancellationToken);   // This puts task in faulted, not canceled
    await Task.Delay(5000, _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";
}

如果对Task.Run为何如此工作感到好奇,可以深入研究implementation details

© www.soinside.com 2019 - 2024. All rights reserved.