接上文 多线程编程学习笔记——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文本的代码没有放到捕捉的上下文中的,而是在线程池的工做 线程中执行。以下图。