运行以下代码:
using static System.Console;
WriteLine("Handling cancellations and exceptions.");
CancellationTokenSource cts = new();
CancellationToken token = cts.Token;
var transferMoney = Task<string>.Factory.StartNew(
() =>
{
WriteLine($"Initiating the money transfer.");
int progressBar = 0;
WriteLine("Press c to cancel withing 5 sec.");
// Assuming the task will take 5 seconds.
// So, after every second, we'll increase the progress by 20%
for (int i = 0; i < 5; i++)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(1000);
progressBar += 20;
WriteLine($"Progress:{progressBar}%");
}
return "Money transfer is completed.";
}, token);
var input = ReadKey().KeyChar;
if (input.Equals('c'))
{
WriteLine("\nCancellation is requested.");
cts.Cancel();
}
try
{
transferMoney.Wait();
WriteLine(transferMoney.Result);
}
catch (AggregateException ae)
{
ae.Handle(e =>
{
WriteLine($"Caught error : {e.Message}");
return true;
});
}
catch (OperationCanceledException oce)
{
WriteLine($"Caught error due to cancellation :{oce.Message}");
}
WriteLine($"Payment processing status: {transferMoney.Status}");
WriteLine("Thank you, visit again!");
这是一个示例输出(预期行为 - 毫无疑问):
Handling cancellations and exceptions.
Initiating the money transfer.
Press c to cancel withing 5 sec.
Progress:20%
Progress:40%
Progress:60%
c
Cancellation is requested.
Progress:80%
Caught error : A task was canceled.
Payment processing status: Canceled
Thank you, visit again!
现在使用以下任一语句更新 try 块:
try
{
//transferMoney.Wait();
transferMoney.Wait(token);
//await transferMoney; //Same observation
// There is no change in the remaining code
并再次运行代码。这是一个示例输出。
Handling cancellations and exceptions.
Initiating the money transfer.
Press c to cancel withing 5 sec.
Progress:20%
Progress:40%
c
Cancellation is requested.
**Caught error due to cancellation :The operation was canceled.**
Payment processing status: Running
Thank you, visit again!
请注意,这次 AggregateException 的 catch 块不足以处理 OperationCanceledException。我想知道这背后的原因。你能分享一下你的想法吗——我错过了什么? [补充说明:我知道在之前的情况下, wait() 只能抛出 AggregateException 但这个重载版本的 Wait(即 Wait(token))也可以抛出 OperationCancelledException 。 ]
Task.Wait(CancellationToken)
文档:
但是一旦取消标记被取消,等待就会被取消,并抛出
。OperationCanceledException
还有
方法创建一个可取消的等待;也就是说,它会导致当前线程等待,直到发生以下情况之一:Wait(CancellationToken)
- 任务完成。
- 取消令牌被取消。在这种情况下,调用
方法会抛出Wait(CancellationToken)
。OperationCanceledException
在第二种情况下,您的等待被取消,因此您得到
OperationCanceledException
,即该方法可以理解(受监视令牌的)取消已经发生,而不是一些随机错误(这将进入“任务完成”情况并导致在AggregateException
).
OperationCanceledException
不继承自 AggregateException
,因此 catch(AggregateException)
无法处理它。
如果任务执行期间发生任何异常,则 OperationCanceledException
的优先级高于任何其他异常。来自Task.Wait
源代码:
// If cancellation was requested and the task was canceled, throw an
// OperationCanceledException. This is prioritized ahead of the ThrowIfExceptional
// call to bring more determinism to cases where the same token is used to
// cancel the Wait and to cancel the Task. Otherwise, there's a race condition between
// whether the Wait or the Task observes the cancellation request first,
// and different exceptions result from the different cases.
if (IsCanceled) cancellationToken.ThrowIfCancellationRequested();
// If an exception occurred, or the task was cancelled, throw an exception.
ThrowIfExceptional(true);