接上文 多線程編程學習筆記——async和await(一)html
接上文 多線程編程學習筆記——async和await(二)編程
5、 處理異步操做中的異常windows
本示例學習如何在異步函數中處理異常,學習如何對多個並行的異步操做使用await時聚合異常。多線程
1.程序示例代碼以下。異步
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; namespace ThreadAsyncDemo { class Program { static void Main(string[] args) { Console.WriteLine(string.Format("----- 處理異步操做中的異常----")); Task t = AsyncProcess(); t.Wait(); Console.Read(); } async static Task AsyncProcess() { Console.WriteLine(string.Format("----- 1 單個異常處理----")); try { string result =await GetInfoAsync("Task 1", 2); Console.WriteLine(result); } catch (Exception ex) { Console.WriteLine(string.Format("異常信息:{0}",ex.Message)); } Console.WriteLine(string.Format(" ------- ----")); Console.WriteLine(string.Format("----- 2 多個異常處理----")); Task<string> task1 = GetInfoAsync("Task 1", 3); Task<string> task2 = GetInfoAsync("Task 2",2); try { string[] results = await Task.WhenAll(task1, task2); Console.WriteLine((string.Format("結果數量:{0}",results.Length))); foreach (var item in results) { Console.WriteLine(item); } } catch (Exception ex) { Console.WriteLine(string.Format("異常信息:{0}", ex.Message)); } Console.WriteLine(string.Format(" ------- ----")); Console.WriteLine(string.Format("----- 3 多個異常處理 在AggregateException----")); Task<string> task3 = GetInfoAsync("Task 3", 3); Task<string> task4 = GetInfoAsync("Task 4", 2); Task<string[]> task5 = Task.WhenAll(task3, task4); try { string[] results5 = await task5; Console.WriteLine((string.Format("結果數量:{0}", results5.Length))); foreach (var item in results5) { Console.WriteLine(item); } } catch { var aex = task5.Exception.Flatten(); //獲取AggregateException var exs = aex.InnerExceptions; Console.WriteLine(string.Format("異常信息:{0}", exs.Count)); int i = 0; foreach (var item in exs) { i++; Console.WriteLine(string.Format(" ----- {0} ----",i)); Console.WriteLine(string.Format("異常信息:{0}", item.Message)); } } } async static Task<string> GetInfoAsync(string name,int second) { Console.WriteLine(string.Format(" Task {0} 正在運行在線程 ID={1}上。這個工做線程是不是線程池中的線程:{2}", name,
Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread)); await Task.Delay(TimeSpan.FromSeconds(second)); throw new Exception(string.Format("{0} 拋出異常信息!", name)); } } }
2.程序運行結果,以下圖。async
這個程序一共有三個場景來學習使用async與await時,關於異常處理的常見狀況。函數
第一種狀況最簡單,與常見的同步 代碼幾乎同樣,咱們只使用try catch便可獲取異常信息。post
第二種狀況是對一個以上的異步異常使用await時,則只能從aggregateexception對象中獲得第一個異常。學習
第三種狀況中,咱們使用aggregateException中的flatten方法將層級異常放入一個列表,並從中提取全部的底層異常。ui
6、 避免使用捕獲的同步上下文
本示例學習使用await來獲取異步操做結果時,同步上下文行爲的結節,並如何在什麼時候關閉同步上下文流。
默認狀況下,await操做符會嘗試捕獲同步上下文,並在其中執行代碼。使用await操做符不會發生死鎖的狀況,由於當等待結果時並不會阻塞UI線程。
2.在「引用管理器」中找到System.Windows.Forms引用 ,並添加。以下圖。
3. 添加一個windows窗體。以下圖。
4. 在windows窗體中,添加按鈕與文本框,界面以下圖。
6 .代碼以下圖。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace ThreadAsyncDemo { public partial class FormContext : Form { public FormContext() { InitializeComponent(); } async static Task<TimeSpan> TestNoContext() { const int interationsNumber = 100000; var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < interationsNumber; i++) { var t = Task.Run(() => { }); await t.ConfigureAwait(continueOnCapturedContext: false); } sw.Stop(); return sw.Elapsed; } async static Task<TimeSpan> Test() { const int interationsNumber = 100000; var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < interationsNumber; i++) { var t = Task.Run(() => { }); await t; } sw.Stop(); return sw.Elapsed; } private async void buttonAsync_Click(object sender, EventArgs e) { textBoxMsg.Text = "程序開始計算。。。。"; TimeSpan resultWithContext = await Test(); TimeSpan resultNoContext = await TestNoContext(); //TimeSpan resultNoContext = await TestNoContext().ConfigureAwait(false); var sb = new StringBuilder(); sb.AppendLine(string.Format("有上下文的運行時間:{0}",resultWithContext)); sb.AppendLine(string.Format("沒有上下文的運行時間:{0}", resultNoContext)); sb.AppendLine(string.Format("有上下文的運行時間/沒有上下文的運行時間:{0:0.00}",
resultWithContext.TotalMilliseconds/ resultNoContext.TotalMilliseconds)); textBoxMsg.Text += "\r\n\r\n"; textBoxMsg.Text += sb.ToString(); } } }
7.程序運行結果,以下圖。
這是一個windowform程序,咱們在winfowform程序中建立了一個按鈕點擊事件,當點擊這個按鈕時,運行兩個異步操做,其中一個異步操做使用了await操做符,別一個使用了帶false參數值的configureAwait方法。False參數明確指出咱們不能對其使用捕獲的同步上下文來運行後續的代碼。在每一個操做中,咱們計算了執行完成花費的時間,而後將各自的時間比較顯示在屏幕上。
從中咱們發現await操做符花費了更多的時間來完成。這是由於咱們向UI線程中放入了上千的後續操做任務,這就形成了使用消息循環來異步執行這些任務。而帶有false參數值 的configureAwait方法是一個更高效的解決方式。
咱們還能夠進行如下操做,當程序運行以後,在點擊按鈕後,等待結果時,能夠隨機拖拽應用程序窗口從一側到另外一側,此時你注意一下,會發現捕獲同步上下文的代碼執行速度變慢了。以下圖。
最後,咱們來看看相反的狀況。在代碼的點擊事件中,取消註釋行,並註釋掉緊挨着它的前一行代碼。運行程序,咱們將看到多線程控制訪問的異常。由於設置Label文本的代碼沒有放到捕捉的上下文中的,而是在線程池的工做 線程中執行。以下圖。