取消以下任务时,该任务的状态不是“已取消但有故障:
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的可用性问题。有人可以帮忙吗?
[Task.Run
确实希望我们提供取消令牌以正确传播取消状态,请参阅:
Faulted vs Canceled task status after CancellationToken.ThrowIfCancellationRequested
如果我们使用接受Task.Run
或Action
委托的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。