執行幾個任務,等待它們所有完成。html
UI界面加載多個模塊,併發請求express
Task.WhenAll
傳入若干任務,當全部任務完成時,返回一個完成的任務。api
重載方法數組
Task WhenAll(IEnumerable<Task>)
Task WhenAll(params Task[])
Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>>)
Task<TResult[]> WhenAll<TResult>(params Task<TResult>[])
var task1 = Task.Delay(TimeSpan.FromSeconds(1)); var task2 = Task.Delay(TimeSpan.FromSeconds(2)); var task3 = Task.Delay(TimeSpan.FromSeconds(3)); await Task.WhenAll(task1, task2, task3);
當任務返回結果類型相同,全部任務完成返回的是,存着每一個任務執行結果的數組。併發
var task1 = Task.FromResult(1); var task2 = Task.FromResult(2); var task3 = Task.FromResult(3); int[] array = await Task.WhenAll(task1, task2, task3); //{1,2,3}
返回的數組中結果的順序,並不是可控,如上述例子中,只是結果爲包含了1,2,3的數組,順序是不定的。app
書中不建議使用以 IEnumerable 類型做爲參數的重載。文中沒有介紹做者不建議的緣由。我找到做者我的博客的一篇文中中提到以下文字(文章地址:https://blog.stephencleary.com/2015/01/a-tour-of-task-part-7-continuations.html)異步
The
IEnumerable<>
overloads allow you to pass in a sequence of tasks, such as a LINQ expression. The sequence is immediately reified (i.e., copied to an array). For example, this allows you to pass the results of aSelect
expression directly toWhenAll
. Personally, I usually prefer to explicitly reify the sequence by callingToArray()
so that it’s obvious that’s what’s happening, but some folks like the ability to pass the sequence directly in.async
該段文字解釋了做者更喜歡使用LINQ結合ToArray的方式使用異步,由於做者認爲代碼會更清晰。書中有例子,以下所示:this
static async Task<string> DownloadAllAsync(IEnumerable<string> urls) { var httpClient = new HttpClient(); // 定義每個 url 的使用方法。 var downloads = urls.Select(url => httpClient.GetStringAsync(url)); // 注意,到這裏,序列尚未求值,因此全部任務都還沒真正啓動。 // 下面,全部的 URL 下載同步開始。 Task<string>[] downloadTasks = downloads.ToArray(); // 到這裏,全部的任務已經開始執行了。 // 用異步方式等待全部下載完成。 string[] htmlPages = await Task.WhenAll(downloadTasks); return string.Concat(htmlPages); }
若是報錯記得添加以下引用url
using System.Linq; using System.Net.Http;
var task1 = ......; var task2 = ......; var task3 = ......; Task allTasks = Task.WhenAll(task1, task2, task3);
以上述僞代碼爲例說明allTasks的狀態
上面提到了異常處理,當一個task異常,該異常會被allTasks接收,當多個task異常,這些異常也都會被allTasks接收。可是task1拋異常,task2也出異常,可是try catch 處理await Task.WhenAll(task1, task2);
只能抓取其中某一個異常。如何獲取全部異常呢?書中列舉了兩種處理方法,代碼以下
拋出異常的方法
static async Task ThrowNotImplementedExceptionAsync() { throw new NotImplementedException(); } static async Task ThrowInvalidOperationExceptionAsync() { throw new InvalidOperationException(); }
第一種處理方式,只能獲取其中一個異常
static async Task ObserveOneExceptionAsync() { var task1 = ThrowNotImplementedExceptionAsync(); var task2 = ThrowInvalidOperationExceptionAsync(); try { await Task.WhenAll(task1, task2); } catch (Exception ex) { // ex 要麼是 NotImplementedException,要麼是 InvalidOperationException //... } }
第二種處理方式,能夠獲取全部異常
static async Task ObserveAllExceptionsAsync() { var task1 = ThrowNotImplementedExceptionAsync(); var task2 = ThrowInvalidOperationExceptionAsync(); Task allTasks = Task.WhenAll(task1, task2); try { await allTasks; } catch { AggregateException allExceptions = allTasks.Exception; //... } }
兩種方式的區別是,await調用Task.WhenAll
返回的Task對象,即例子中的allTasks,代碼await allTasks;
做者在書中將對Task.WhenAll的異常處理放在了討論中,並說明了本身的處理方式
使用 Task.WhenAll 時,我通常不會檢查全部的異常。一般狀況下,只處理第一個錯誤就足夠了,不必處理所有錯誤。
顯然做者更中意第一種方式。那麼你呢?
參考文章:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task.whenall?view=netcore-2.2