.NET 6 新特性 WaitAsync

.NET 6 新特性WaitAsync

Intro

在 .NET 6 里新增加了一个 WaitAsync 的方法,用来异步地等待一个任务完成,异步等待的时候可以指定一个 Timeout 时间或者一个取消令牌 CancellationToken,在之前的版本中只有一个同步的 Wait 会等待任务的完成,不支持比较好的任务超时或取消处理,如果要实现的话要自己写扩展,很多开源项目甚至微软的项目里会有一个 TimeoutAfter 之类的扩展方法,有了 WaitAsync 之后就可以取代这些扩展了

Definition

新加的 WaitAsync 是一个扩展方法,定义如下:

public static Task WaitAsync(this Task task, TimeSpan timeout); public static Task WaitAsync(this Task task, CancellationToken cancellationToken); public static Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken); // 泛型版本 public static Task<TResult> WaitAsync<TResult>(this Task<TResult> task, TimeSpan timeout); public static Task<TResult> WaitAsync<TResult>(this Task<TResult> task, CancellationToken cancellationToken); public static Task<TResult> WaitAsync<TResult>(this Task<TResult> task, TimeSpan timeout, CancellationToken cancellationToken); 

Timeout Sample

来看一个 WaitAsync Timeout 的 使用示例:

var tasks = new List<Task>(); tasks.AddRange(new[]  {      Task.Delay(TimeSpan.FromSeconds(5)),     Task.Delay(TimeSpan.FromSeconds(8)),     Task.Delay(TimeSpan.FromSeconds(6)) }); Task task = Task.WhenAll(tasks); try {     await task.WaitAsync(TimeSpan.FromSeconds(3)); } catch (TimeoutException) {     Console.WriteLine(nameof(TimeoutException)); } finally {     Console.WriteLine(string.Join(",", tasks.Select(t => t.Status.ToString())));     Console.WriteLine(task.Status); }  await Task.Delay(TimeSpan.FromSeconds(5)); Console.WriteLine(string.Join(",", tasks.Select(t => t.Status.ToString()))); Console.WriteLine(task.Status); 

上面是一个使用 Timeout 的一个示例,当 Timeout 时间到了之后 Task 还没完成就会 throw 一个 TimeoutException,但是并不会影响原来的任务继续执行,除非自己能够在 exception 的时候将原来的 Task 给中止,上面示例的输出结果如下:

TimeoutException WaitingForActivation,WaitingForActivation,WaitingForActivation WaitingForActivation RanToCompletion,RanToCompletion,RanToCompletion RanToCompletion 

可以看到,即使发生了 Timeout 也不会影响原来 Task 的执行

Cancellation Sample

接着来看一下 CancellationToken 的使用示例

var cts = new CancellationTokenSource(); var tasks = new List<Task>(); tasks.AddRange(new[] {     Task.Delay(TimeSpan.FromSeconds(4)),     Task.Delay(TimeSpan.FromSeconds(8)),     Task.Delay(TimeSpan.FromSeconds(6)) }); var task = Task.WhenAll(tasks); try {     cts.CancelAfter(TimeSpan.FromSeconds(5));     await task.WaitAsync(cts.Token); } catch (TaskCanceledException) {     Console.WriteLine("Task cancelled"); } finally {     Console.WriteLine(string.Join(",", tasks.Select(t => t.Status.ToString())));     Console.WriteLine(task.Status); }  await Task.Delay(TimeSpan.FromSeconds(5)); Console.WriteLine(string.Join(",", tasks.Select(t => t.Status.ToString()))); Console.WriteLine(task.Status); 

输出结果如下:

Task cancelled RanToCompletion,WaitingForActivation,WaitingForActivation WaitingForActivation RanToCompletion,RanToCompletion,RanToCompletion RanToCompletion 

使用 CancellationToken 的时候抛出的异常是 TaskCanceledException,而不是前面的 TimeoutException

而抛异常的行为和前面一样,并不会影响原来 Task 的状态

Another Sample

我们再来看一个既使用 Timeout 又使用取消令牌的一个示例吧

 try {     await Task.Delay(TimeSpan.FromSeconds(5))       .WaitAsync(TimeSpan.FromSeconds(3), CancellationToken.None); } catch(Exception ex) {     Console.WriteLine(ex.GetType().Name); }  try {     using var cancellationTokenSource = new CancellationTokenSource();     cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(2));     await Task.Delay(TimeSpan.FromSeconds(5))       .WaitAsync(TimeSpan.FromSeconds(10), cancellationTokenSource.Token); } catch(Exception ex) {     Console.WriteLine(ex.GetType().Name); } 

输出结果如下:

TimeoutException TaskCanceledException 

可以看出来哪一个条件 WaitAsync 的条件先满足,抛出的就是哪一个对应的异常,两个异常都没有的就可以正常结束

More

WaitAsync 方法可以解决很多需要等待或者设置 Timeout 的场景,官方支持了这个 API 以后很多 TimeoutAfter/WithCancellationToken 之类的扩展方法可以去掉了

WaitAsync 抛出的异常需要针对处理,如果是 Timeout 则是 TimeoutException 如果是 CancellationToken Cancel 引发的异常则是 TaskCanceledException

References

  • https://github.com/dotnet/runtime/pull/48842
  • https://github.com/dotnet/runtime/blob/v6.0.0-preview.7.21377.19/src/libraries/Common/tests/System/Threading/Tasks/TaskTimeoutExtensions.cs
  • https://github.com/WeihanLi/SamplesInPractice/blob/master/net6sample/WaitAsyncSample/Program.cs

您可能还会对下面的文章感兴趣: