
异步编程基础
发布日期:2021-05-08 23:15:54
浏览次数:12
分类:博客文章
本文共 6693 字,大约阅读时间需要 22 分钟。
1. 概述
前面的文章介绍了标识了 async
和 await
的代码,是怎么被线程执行的。
下面介绍一些类库和常用的API。
2. 报告进度
使用 IProgress<T>
和 Progress<T>
类型
- 构造
Progress<T>
实例时捕获当前 同步上下文 实例; Progress<T>
实例的ProgressChanged
事件被调用时使用上面捕获的同步上下文;- 如果在执行构造函数的线程没有同步上下文时(隐含使用的Default同步上下文),则将在 ThreadPool 中调用事件
static async Task DoProgressAsync(int count, IProgress 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 (); progress.ProgressChanged += (sender, args) => { System.Console.WriteLine($"{args}/{count}"); }; await DoProgressAsync(count, progress);}
输出为:
1/52/53/54/55/5
3. 等待一组任务完成
Task.WhenAll
方法有以 IEnumerable
类型作为参数的重载,但建议大家不要使用。
- 调用
ToList
或ToArray
方法后,序列中没有启动的任务就开始了
static async Task DownloadAllAsync(){ Stopwatch sw = new Stopwatch(); sw.Start(); IEnumerableurls = 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 [] downloadTasks = downloads.ToArray(); // 到这里,所有的任务已经开始执行了。 Console.WriteLine($"await Task.WhenAll"); // 用异步方式等待所有下载完成。 string[] htmlPages = await Task.WhenAll(downloadTasks); Console.WriteLine($"jobs done.");}
输出为:
https://www.baidu.com/:starthttps://cn.bing.com/:startawait Task.WhenAllhttps://www.baidu.com/:270msjobs done.; 由于返回的是请求的 Task 不是 ContinueWith 的打印 Taskhttps://cn.bing.com/:1089ms
4. 异常处理
- 如果有一个任务抛出异常,则
Task.WhenAll
会出错,并把这个异常放在返回的Task
中 - 如果多个任务抛出异常,则这些异常都会放在返回的
Task
中 - 如果这个
Task
在被await
调用,就只会抛出该异步方法的一个异常 - 如果要得到每个异常,可以检查
Task.WhenAll
返回的Task
的Exception
属性:
示例:
static async Task ThrowNotImplementedExceptionAsync(){ await Task.Delay(10); throw new NotImplementedException();}static async Task ThrowInvalidOperationExceptionAsync(){ TaskCompletionSource completionSource = new TaskCompletionSource (); 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; }); }}
输出为:
OneExceptionNotImplementedException
AllExceptionsNotImplementedExceptionInvalidOperationException
5. 等待任意一个任务完成
// 返回第一个响应的 URL 的数据长度。private static async Task FirstRespondingUrlAsync(){ string urlA = "https://www.baidu.com/"; string urlB = "https://cn.bing.com/"; var httpClient = new HttpClient(); // 并发地开始两个下载任务。 TaskdownloadTaskA = httpClient.GetByteArrayAsync(urlA); Task downloadTaskB = httpClient.GetByteArrayAsync(urlB); // 等待任意一个任务完成。 Task 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: downloadTaskAdownloadTaskA: RanToCompletiondownloadTaskB: WaitingForActivation
如果这个任务完成时有异常,这个异常也不会传递给 Task.WhenAny
返回的 Task
对象。因此,通常需要在 Task
对象完成后继续使用 await
。
第一个任务完成后,考虑是否要取消剩下的任务。如果其他任务没有被取消,也没有被继续 await
,那它们就处于被遗弃的状态。被遗弃的任务会继续运行直到完成,它们的结果会被忽略,抛出的任何异常也会被忽略。
//每个任务需要等到Trace.WriteLine执行完才能执行下一个static async Task DelayAndReturnAsync(int val){ await Task.Delay(TimeSpan.FromSeconds(val)); return val;}// 当前,此方法输出“2”,“3”,“1”。 // 我们希望它先输出先完成的,期望 输出“1”,“2”,“3”。 static async Task ProcessTasksAsync1(){ // 创建任务队列。 Task taskA = DelayAndReturnAsync(2); Task taskB = DelayAndReturnAsync(3); Task 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 taskA = DelayAndReturnAsync(2); Task taskB = DelayAndReturnAsync(3); Task 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");}
输出为:
ProcessTasksAsync12313007msProcessTasksAsync21233004ms
6. 避免上下文延续
默认情况下,一个 async
方法在被 await
调用后恢复运行时,会在原来的上下文中运行。 如果是 UI上下文 ,并且有大量的 async
方法在 UI上下文 中恢复,就会引起性能上的问题。
ConfigureAwait(true)
延续上下文(执行完异步await
,回到同步上下文)ConfigureAwait(false)
不延续上下文(执行完异步await
,由于没有记录之前的同步上下文,后续代码在 Default上下文 中运行)
7. async void
处理 async void
方法的异常有一个办法:
- 一个异常从
async void
方法中传递出来时,会在其同步上下文中引发出来 - 当
async void
方法启动时,同步上下文 就处于激活状态- 如果系统运行环境有特定的 同步上下文(如:UI同步上下文,ASP.Net同步上下文),通常就可以在全局范围内处理这些顶层的异常
- PF 有
Application.DispatcherUnhandledException
, - WinRT 有
Application.UnhandledException
- ASP.NET 有
Application_Error
- PF 有
- 如果系统运行环境有特定的 同步上下文(如:UI同步上下文,ASP.Net同步上下文),通常就可以在全局范围内处理这些顶层的异常
发表评论
最新留言
做的很好,不错不错
[***.243.131.199]2025年04月16日 17时32分43秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
消息队列 RocketMQ 并发量十万级
2021-05-09
ReactJs入门教程-精华版
2021-05-09
乐观锁悲观锁应用
2021-05-09
简单说说TCP三次握手、四次挥手机制
2021-05-09
.net Core 使用IHttpClientFactory请求
2021-05-09
多线程之旅(准备阶段)
2021-05-09
Python 之网络式编程
2021-05-09
MySql5.5安装步骤及MySql_Front视图配置
2021-05-09
springmvc Controller详解
2021-05-09
mybatis #{}和${}区别
2021-05-09
Java Objects工具类重点方法使用
2021-05-09
Java内存模型(JMM)
2021-05-09
AQS相关
2021-05-09
abp(net core)+easyui+efcore实现仓储管理系统——多语言(十)
2021-05-09
WCF学习之旅—第三个示例之一(二十七)
2021-05-09