Microsoft recommends通过将try/catch
与OperationCanceledException
一起使用来处理已取消的任务。同时,可以使用.ContinueWith()
将执行的任务继续包装,这将吞下OperationCanceledException
而不抛出。看起来,延续仍在内部处理异常,但不会冒泡。
考虑到热执行路径上的可取消任务(采用取消令牌),是否仍建议使用try/catch(OperationCanceledException)
或延续方法?
例如:
await Task.Delay(delayValue, cts.Token);
可以通过
处理try
{
await Task.Delay(delayValue, cts.Token);
}
catch(OperationCanceledException)
{
// token triggered cancellation
return;
}
或通过
var task = await Task.Delay(delayValue, cts.Token).ContinueWith(t => t);
if (task.IsCancelled)
{
// token triggered cancellation
return;
}
答案 0 :(得分:1)
严格关注问题而不是动机,我使用BenchmarkDotNet进行了基准测试:
[MemoryDiagnoser]
public class CancelBench
{
private int delayValue = 15;
private CancellationToken cancellationToken = new CancellationToken(true);
[Benchmark]
public async Task<bool> Exception()
{
try
{
await Task.Delay(delayValue, cancellationToken);
}
catch (OperationCanceledException)
{
// token triggered cancellation
return true;
}
return false;
}
[Benchmark]
public async Task<bool> ContinueWith()
{
var task = await Task.Delay(delayValue, cancellationToken).ContinueWith(t => t);
if (task.IsCanceled)
{
// token triggered cancellation
return true;
}
return false;
}
}
根据结果,ContinueWith
方法快5倍:
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -------------------------- |------------:|-----------:|----------:|-------:|-------:|-------:|----------:| Exception | 13,823.6 ns | 105.359 ns | 93.398 ns | 0.0916 | - | - | 496 B | ContinueWith | 2,843.0 ns | 14.300 ns | 13.376 ns | 0.0496 | 0.0076 | 0.0038 | 276 B |
也就是说,通过避免跳转到线程池进行继续操作,您可以甚至更快:
[Benchmark]
public async Task<bool> ContinueWithSynchronously()
{
var task = await Task.Delay(delayValue, cancellationToken).ContinueWith(t => t, TaskContinuationOptions.ExecuteSynchronously);
if (task.IsCanceled)
{
// token triggered cancellation
return true;
}
return false;
}
ContinueWithSynchronously | 505.2 ns | 2.581 ns | 2.414 ns | 0.0277 | - | - | 148 B |
现在我们快了27倍。
当然,这引出了一个问题,即节省10µs是否实际上会对您的应用产生影响。如果是这样,那么您可能希望完全避免使用async
。
[Benchmark]
public Task<bool> NoAsync()
{
return Task.Delay(delayValue, cancellationToken).ContinueWith(t => t.IsCanceled, TaskContinuationOptions.ExecuteSynchronously);
}
NoAsync | 397.7 ns | 5.290 ns | 4.948 ns | 0.0281 | - | - | 148 B |
编辑:我需要花一些时间在最后一个基准上,因为我真的感到惊讶,它分配的内存与异步版本一样多。我想知道编译器是否已经在幕后进行了优化(有人谈论在.net核心上添加该功能,但是我很惊讶它已经被移植到.net框架中了),或者可能会发生某些事情使用BenchmarkDotNet。
答案 1 :(得分:0)
await
在幕后会将您的代码分为启动任务和继续任务。许多等待将导致许多任务。这是使用异步/等待的最大优势-您不必处理这种复杂性。最好不要混合它们。无论如何,等待很简单,您不能添加太多选项。另一方面,ContinueWith
有几个选项。其中包括允许您添加TaskContinuationOptions的重载。在不知道最终目的是什么的情况下,您可以同时使用延续和异常:
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
await Task.Delay(TimeSpan.FromSeconds(5), cts.Token)
.ContinueWith(t => Console.WriteLine("Continued"), TaskContinuationOptions.NotOnCanceled);
Console.WriteLine("Hihi...");
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancelled");
}
Console.ReadLine();