>>返回《C# 併發編程》
html
前面的文章介紹了標識了 async
和 await
的代碼,是怎麼被線程執行的。編程
下面介紹一些類庫和經常使用的API。異步
使用 IProgress<T>
和 Progress<T>
類型async
Progress<T>
實例時捕獲當前 同步上下文 實例;Progress<T>
實例的ProgressChanged
事件被調用時使用上面捕獲的同步上下文;static async Task DoProgressAsync(int count, IProgress<int> progress = null) { for (int i = 0; i < count; i++) { await Task.Delay(200); if (progress != null) progress.Report(i + 1); } } static async Task CallProgressAsync() { int count = 5; var progress = new Progress<int>(); progress.ProgressChanged += (sender, args) => { System.Console.WriteLine($"{args}/{count}"); }; await DoProgressAsync(count, progress); }
輸出爲:異步編程
1/5 2/5 3/5 4/5 5/5
Task.WhenAll
方法有以 IEnumerable
類型做爲參數的重載,但建議你們不要使用。函數
ToList
或 ToArray
方法後,序列中沒有啓動的任務就開始了static async Task DownloadAllAsync() { Stopwatch sw = new Stopwatch(); sw.Start(); IEnumerable<string> urls = new string[]{ "https://www.baidu.com/", "https://cn.bing.com/" }; var httpClient = new HttpClient(); // 定義每個 url 的使用方法。 var downloads = urls.Select(url => { Console.WriteLine($"{url}:start"); var res = httpClient.GetStringAsync(url); res.ContinueWith(t => Console.WriteLine($"{url}:{sw.ElapsedMilliseconds}ms")); return res; }); // 注意,到這裏,序列尚未求值,因此全部任務都還沒真正啓動。 // 下面,全部的 URL 下載同步開始。 Task<string>[] downloadTasks = downloads.ToArray(); // 到這裏,全部的任務已經開始執行了。 Console.WriteLine($"await Task.WhenAll"); // 用異步方式等待全部下載完成。 string[] htmlPages = await Task.WhenAll(downloadTasks); Console.WriteLine($"jobs done."); }
輸出爲:性能
https://www.baidu.com/:start https://cn.bing.com/:start await Task.WhenAll https://www.baidu.com/:270ms jobs done. ; 因爲返回的是請求的 Task 不是 ContinueWith 的打印 Task https://cn.bing.com/:1089ms
Task.WhenAll
會出錯,並把這個異常放在返回的 Task
中Task
中Task
在被 await
調用,就只會拋出該異步方法的一個異常Task.WhenAll
返回的 Task
的 Exception
屬性:示例:url
static async Task ThrowNotImplementedExceptionAsync() { await Task.Delay(10); throw new NotImplementedException(); } static async Task<int> ThrowInvalidOperationExceptionAsync() { TaskCompletionSource<int> completionSource = new TaskCompletionSource<int>(); completionSource.TrySetException(new InvalidOperationException()); return await completionSource.Task; } static async Task ObserveOneExceptionAsync() { System.Console.WriteLine("OneException"); var task1 = ThrowNotImplementedExceptionAsync(); var task2 = ThrowInvalidOperationExceptionAsync(); try { await Task.WhenAll(task1, task2); } catch (Exception ex) { // ex 要麼是 NotImplementedException,要麼是 InvalidOperationException System.Console.WriteLine(ex.GetType().Name); } } static async Task ObserveAllExceptionsAsync() { System.Console.WriteLine("AllExceptions"); var task1 = ThrowNotImplementedExceptionAsync(); var task2 = ThrowInvalidOperationExceptionAsync(); Task allTasks = Task.WhenAll(task1, task2); try { await allTasks; } catch { AggregateException allExceptions = allTasks.Exception; allExceptions.Handle(ex => { System.Console.WriteLine(ex.GetType().Name); return true; }); } }
輸出爲:spa
OneException NotImplementedException
AllExceptions NotImplementedException InvalidOperationException
// 返回第一個響應的 URL 的數據長度。 private static async Task<int> FirstRespondingUrlAsync() { string urlA = "https://www.baidu.com/"; string urlB = "https://cn.bing.com/"; var httpClient = new HttpClient(); // 併發地開始兩個下載任務。 Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA); Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB); // 等待任意一個任務完成。 Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB); // 返回從 URL 獲得的數據的長度。 byte[] data = await completedTask; Console.WriteLine($"Finish: {(completedTask == downloadTaskA ? nameof(downloadTaskA) : nameof(downloadTaskA))}"); Console.WriteLine($"downloadTaskA: {downloadTaskA.Status}"); Console.WriteLine($"downloadTaskB: {downloadTaskB.Status}"); return data.Length; }
輸出爲:
Finish: downloadTaskA downloadTaskA: RanToCompletion downloadTaskB: WaitingForActivation
若是這個任務完成時有異常,這個異常也不會傳遞給 Task.WhenAny
返回的 Task
對象。所以,一般須要在 Task
對象完成後繼續使用 await
。
第一個任務完成後,考慮是否要取消剩下的任務。若是其餘任務沒有被取消,也沒有被繼續 await
,那它們就處於被遺棄的狀態。被遺棄的任務會繼續運行直到完成,它們的結果會被忽略,拋出的任何異常也會被忽略。
//每一個任務須要等到Trace.WriteLine執行完才能執行下一個 static async Task<int> DelayAndReturnAsync(int val) { await Task.Delay(TimeSpan.FromSeconds(val)); return val; } // 當前,此方法輸出「2」,「3」,「1」。 // 咱們但願它先輸出先完成的,指望 輸出「1」,「2」,「3」。 static async Task ProcessTasksAsync1() { // 建立任務隊列。 Task<int> taskA = DelayAndReturnAsync(2); Task<int> taskB = DelayAndReturnAsync(3); Task<int> taskC = DelayAndReturnAsync(1); var tasks = new[] { taskA, taskB, taskC }; // 按順序 await 每一個任務。 foreach (var task in tasks) { var result = await task; Console.WriteLine(result); } } //不等Trace.WriteLine切任務並行的解決方案 // 如今,這個方法輸出「1」,「2」,「3」。 static async Task ProcessTasksAsync2() { // 建立任務隊列。 Task<int> taskA = DelayAndReturnAsync(2); Task<int> taskB = DelayAndReturnAsync(3); Task<int> taskC = DelayAndReturnAsync(1); var tasks = new[] { taskA, taskB, taskC }; var processingTasks = tasks.Select(async t => { var result = await t; Console.WriteLine(result); }).ToArray(); // 等待所有處理過程的完成。 await Task.WhenAll(processingTasks); } static async Task ProcessTasksAsyncExe() { Stopwatch sw = Stopwatch.StartNew(); System.Console.WriteLine("ProcessTasksAsync1"); await ProcessTasksAsync1(); System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms"); sw.Restart(); System.Console.WriteLine(); System.Console.WriteLine("ProcessTasksAsync2"); await ProcessTasksAsync2(); System.Console.WriteLine($"{sw.ElapsedMilliseconds}ms"); }
輸出爲:
ProcessTasksAsync1 2 3 1 3007ms ProcessTasksAsync2 1 2 3 3004ms
默認狀況下,一個 async
方法在被 await
調用後恢復運行時,會在原來的上下文中運行。 若是是 UI上下文 ,而且有大量的 async
方法在 UI上下文 中恢復,就會引發性能上的問題。
ConfigureAwait(true)
延續上下文(執行完異步 await
,回到同步上下文)ConfigureAwait(false)
不延續上下文(執行完異步 await
,因爲沒有記錄以前的同步上下文,後續代碼在 Default上下文 中運行)處理 async void
方法的異常有一個辦法:
async void
方法中傳遞出來時,會在其同步上下文中引起出來async void
方法啓動時,同步上下文 就處於激活狀態
Application.DispatcherUnhandledException
,Application.UnhandledException
Application_Error