天天看點

.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

如果是