.NET 中讓 Task 支援帶超時的非同步等待
Task 自帶有很多等待任務完成的方法,有的是例項方法,有的是靜態方法。有的阻塞,有的不阻塞。不過帶超時的方法只有一個,但它是阻塞的。
本文將介紹一個非阻塞的帶超時的等待方法。
Task 已有的等待方法
Task 例項已經有的等待方法有這些:
▲ Task 例項的等待方法
一個支援取消,一個支援超時,再剩下的就是這兩個的排列組合了。
但是 Task 例項的等待方法都有一個弊端,就是 阻塞 。如果你真的試圖去等待這個 Task,勢必會佔用一個寶貴的執行緒資源。所以通常不建議這麼做。
另外,Task 還提供了靜態的等待方法:
▲ Task 靜態的等待方法
Task.Wait 提供的功能幾乎與 Task 例項的 Wait 方法是一樣的,只是可以等待多個 Task 的例項。而 Task.When 則是真正的非同步等待,不阻塞執行緒的,可以節省一個執行緒資源。
可是,依然只有 Task.Wait 這種阻塞的方法才有超時,Task.When 系列是沒有的。
我們補充一個帶超時的一步等待方法
Task 有一個 Delay
靜態方法,我們是否可以利用這個方法來間接實現非同步非阻塞的等待呢?
答案是可以的,我們有 Task.WhenAny
可以在多個任務的任何一個完成時結束。我們的思路是要麼任務先完成,要麼超時先完成。
於是我們可以先建一個新的 Task,即 Task.Delay(timeout)
,再比較這兩個 Task 的執行先後:
public static async Task<TResult> WaitAsync<TResult>(Task<TResult> task, TimeSpan timeout) { if (await Task.WhenAny(task, Task.Delay(timeout)) == task) { return await task; } throw new TimeoutException("The operation has timed out."); }
考慮延時任務可以取消,於是我們可以使用 CancellationTokenSource
。
將這個方法封裝成 Task
的擴充套件方法:
namespace Walterlv { public static class TaskWaitingExtensions { public static async Task<TResult> WaitAsync<TResult>(this Task<TResult> task, TimeSpan timeout) { using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { var delayTask = Task.Delay(timeout, timeoutCancellationTokenSource.Token); if (await Task.WhenAny(task, delayTask) == task) { timeoutCancellationTokenSource.Cancel(); return await task; } throw new TimeoutException("The operation has timed out."); } } } }
於是我們就可以在任意的 Task
例項上呼叫 Task.WaitAsync
來獲取帶超時的等待了。
參考資料
- [c# - Asynchronously wait for Task
to complete with timeout - Stack Overflow](https://stackoverflow.com/q/4238345/6233938)