异步编程基础
使用 async 和 await 进行异步操作的基础知识,其中只会涉及自然异步操作,如 HTTP 请求、数据库指令、Web 服务调用等。
一、需要通过 异步签名实现同步方法时,返回已完成的任务
如果在继承异步接口或者基类的同时又想同步实现该任务,便可能发生这样的情况。当需要异步接口的简单签名或模拟对象时,或者当需要对异步代码做单元测试时,这项技术特别有用。
1 2 3 4 5 6 7 8 | public interface IMyAsyncInterface { Task< int > GetValueAsync(); Task DoSomethingAsync(); Task<T> NotImplementedAsync<T>(); Task< int > GetValueAsync(CancellationToken token); Task<T> DelayResult<T>(T result, TimeSpan delay); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public class MySynchronousImplementation : IMyAsyncInterface { public async Task<T> DelayResult<T>(T result, TimeSpan delay) { //暂停一段时间,当程序需要异步等待一段时间。在进行单元测试或实现重试延迟时,这是一种常见的场景。 await Task.Delay(delay); return result; } public Task DoSomethingAsync() { //1.异步签名实现同步方法,并且无返回值 try { //DoSomethingSynchronously() return Task.CompletedTask; } catch (Exception ex) { //同步实现可能会失败,就应当捕捉异常并通过Task.FromExceptiion 来返回它们。 return Task.FromException(ex); } } public Task< int > GetValueAsync() { //2.异步签名实现同步方法,并且有返回值 return Task.FromResult(13); } public Task< int > GetValueAsync(CancellationToken token) { //3.带取消功能的任务 if (token.IsCancellationRequested) return Task.FromCanceled< int >(token); return Task.FromResult(13); } public Task<T> NotImplementedAsync<T>() { //4.需要返回异常报错信息 return Task.FromException<T>( new NotImplementedException()); } } |
二、实时更新进度
Progress<T> 会在创建时捕获当前上下文,并在此上下文中调用回调函数。即:如果是在 UI 线程中创建的,那么即使使用异步方法在后台线程中调用 Report ,我们也可以在它的回调函数中更新 UI .
IProgress<T>.Report 方法通常是异步,T 最好定义为值类型。
IProgress<T> 并非专用于异步代码,在耗时较长的同步代码中也可以使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public async Task MyMethodAsync(IProgress< double > progress = null ) { double percentComlete = 0; do { percentComlete += 2; progress?.Report(percentComlete); //报告进度 } while (percentComlete < 100); } public async Task CallMyMethodAsync() { var progress = new Progress< double >(); progress.ProgressChanged += (sender, e) => { Console.WriteLine(e.ToString()); }; await MyMethodAsync(progress); } |
运行结果:
三、等待多个任务完成
场景:有多个任务,需要等等他们全部完成。
3.1 无返回结果的任务
1 2 3 4 | Task task1 = Task.Delay(TimeSpan.FromSeconds(1)); Task task2 = Task.Delay(TimeSpan.FromSeconds(2)); Task task3 = Task.Delay(TimeSpan.FromSeconds(3)); await Task.WhenAll(task1, task2, task3); |
3.2 全部任务都有着相同返回结果类型
1 2 3 4 5 6 7 8 9 | Task< int > task1 = Task.FromResult(3); Task< int > task2 = Task.FromResult(5); Task< int > task3 = Task.FromResult(7); int [] results = await Task.WhenAll(task1, task2, task3); foreach ( int result in results) { Console.WriteLine(result.ToString()); } |
运行结果:3,5,7
3.3. Task.WhenAll() 的 IEnumerable 参数类型重载
1 2 3 4 5 6 7 8 9 | public async Task< string > DownloadAllAsync(HttpClient httpClient, IEnumerable< string > urls) { //为每个 URL 定义请求操作 var downloads = urls.Select(url => httpClient.GetStringAsync(url)); Task< string >[] downloadTasks = downloads.ToArray(); //异步等待所有下载任务完成,并返回结果 string [] htmlPages = await Task.WhenAll(downloadTasks); return string .Concat(htmlPages); } |
3.4 等待多个任务的异常处理
1 2 3 4 5 6 7 8 | async Task ThrowNotimplmentedExceptionAsync() { throw new NotImplementedException(); } async Task ThrowInvalidOperationExceptionAsync() { throw new InvalidOperationException(); } |
3.4.1 仅捕捉其中的一个异常(推荐)
1 2 3 4 5 6 7 8 9 10 11 12 13 | public async Task ObserveOneExceptionAsync() { var task1=ThrowNotimplmentedExceptionAsync(); var task2=ThrowInvalidOperationExceptionAsync(); try { await Task.WhenAll(task1, task2); } catch (Exception ex) { //仅能捕捉一个任务的异常 //ex 不是 NotImplementedException 就是 InvalidOperationException } } |
3.4.2 捕捉全部异常
public async Task ObserveAllExceptionsAsync() { var task1=ThrowNotimplmentedExceptionAsync(); var task2=ThrowInvalidOperationExceptionAsync(); Task allTasks= Task.WhenAll(task1, task2); try { await allTasks; } catch {//捕捉全部任务的异常 AggregateException allExceptions = allTasks.Exception; } }
四、仅等待其中任意一个任务完成
场景:针对同一个操作进行多次独立尝试,并且一次成功即可算作任务完成。比如:同时从多个 Web 服务请求股票行情信息,只要有一个请求有响应任务就算完成。
1 2 3 4 5 6 7 8 9 10 11 | public async Task< int > FirstRespondingUrlAsync(HttpClient client, string urlA, string urlB) { //设置两个下载任务 Task< byte []> downloadTaskA = client.GetByteArrayAsync(urlA); Task< byte []> downloadTaskB = client.GetByteArrayAsync(urlB); //等待两个任务中的任务一个任务完成,即可得到结果 Task< byte []> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB); //返回从 URL 中获取的数据长度 byte [] result = await completedTask; return result.Length; } |
五、等待全部任务完成,同时 其中任意一个任务完成时,实时处理相关结果
1 2 3 4 5 | async Task< int > DelayAndReturnAsync( int value) { await Task.Delay(TimeSpan.FromSeconds(value)); return value; } |
错误示范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public async Task ProcessTasksAsync() { //1.按任务在列表中的顺序依次执行,即:执行顺序为:task1->task2->task3 。(不推荐) //打印结果:2,3,1,理想的打印结果:1,2,3,因为 task3 只等待1秒,最先完成。 Task< int > task1 = DelayAndReturnAsync(2); Task< int > task2 = DelayAndReturnAsync(3); Task< int > task3 = DelayAndReturnAsync(1); Task< int >[] tasks = new [] { task1, task2, task3 }; foreach ( var task in tasks) { //按顺序等待每个任务 var result=await task; Trace.WriteLine(result); } } |
正确示范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | async Task AwaitAndProcessAsync(Task< int > task) { int result=await task; Trace.WriteLine(result); //处理当前已完成的任务结果 } public async Task ProcessTasksAsync2() { //2.1 任务完成时,实时处理结果,与任务在列表中的顺序无关。(推荐) //打印结果:1,2,3 Task< int > task1 = DelayAndReturnAsync(2); Task< int > task2 = DelayAndReturnAsync(3); Task< int > task3 = DelayAndReturnAsync(1); Task< int >[] tasks = new [] { task1, task2, task3 }; IEnumerable<Task> taskQuery= from t in tasks select AwaitAndProcessAsync(t); Task[] processingTasks=taskQuery.ToArray(); //等待全部任务完成 await Task.WhenAll(processingTasks); } public async Task ProcessTasksAsync3() { //2.2 任务完成时,实时处理结果,与任务在列表中的顺序无关。(可选) //打印结果:1,2,3 Task< int > task1 = DelayAndReturnAsync(2); Task< int > task2 = DelayAndReturnAsync(3); Task< int > task3 = DelayAndReturnAsync(1); Task< int >[] tasks = new [] { task1, task2, task3 }; Task[] processingTasks =tasks.Select(async t => { var result=await t; Trace.WriteLine(result); //处理当前已完成的任务结果 }).ToArray(); //等待全部任务完成 await Task.WhenAll(processingTasks); } |
六、当任务完成时,不需要恢复上下文
1 2 3 4 5 6 7 8 9 10 11 12 | async Task ResumeOnContextAsync() { await Task.Delay(TimeSpan.FromSeconds(1)); //当任务执行完成时,继续执行方法时恢复上下文 //dosomething... } async Task ResumeWithoutContextAsync() { await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait( false ); //当任务执行完成时,继续执行方法时,已丢弃上下文 //dosomething... } |
当 async 在 await 之后恢复时,它默认会在主线程当前上下文中继续执行。如果是在UI主线程,并且有很多 async 方法时,可能会引发性能问题。
建议:在UI主线程中,每秒上百个尚可,每秒上千个则过多。
最好从一开始就避免这个问题,针对任意无须在原始上下文中恢复的 async 方法,调用 configureAwait(false) 方法。这样做不会产生任何负作用。
如果有一个 async 方法,其中部分需要上下文,分部不需要上下文,那么可以考虑将其拆分成两个或多个 async 方法,这样在组织代码时,层次会更为清晰。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2022-09-24 安装项目模板IdentityServer4.Templates
2022-09-24 客户端URL路径组织模式
2022-09-24 Service模块读取AppSettings.json
2022-09-24 使用ConnectionMultiplexer访问Redis