本示例學習使用await來獲取非同步操作結果時,同步上下文行為的結節,並如何在何時關閉同步上下文流。 預設情況下,await操作符會嘗試捕獲同步上下文,併在其中執行代碼。使用await操作符不會發生死鎖的情況,因為當等待結果時並不會阻塞UI線程。 ...
五、 處理非同步操作中的異常
本示例學習如何在非同步函數中處理異常,學習如何對多個並行的非同步操作使用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與await時,關於異常處理的常見情況。
第一種情況最簡單,與常見的同步 代碼幾乎一樣,我們只使用try catch即可獲取異常信息。
第二種情況是對一個以上的非同步異常使用await時,則只能從aggregateexception對象中得到第一個異常。
第三種情況中,我們使用aggregateException中的flatten方法將層級異常放入一個列表,並從中提取所有的底層異常。
六、 避免使用捕獲的同步上下文
本示例學習使用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文本的代碼沒有放到捕捉的上下文中的,而是線上程池的工作 線程中執行。如下圖。