异步编程基础
使用 async 和 await 进行异步操作的基础知识,其中只会涉及自然异步操作,如 HTTP 请求、数据库指令、Web 服务调用等。
一、需要通过 异步签名实现同步方法时,返回已完成的任务
如果在继承异步接口或者基类的同时又想同步实现该任务,便可能发生这样的情况。当需要异步接口的简单签名或模拟对象时,或者当需要对异步代码做单元测试时,这项技术特别有用。
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); }
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> 并非专用于异步代码,在耗时较长的同步代码中也可以使用。
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 无返回结果的任务
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 全部任务都有着相同返回结果类型
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 参数类型重载
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 等待多个任务的异常处理
async Task ThrowNotimplmentedExceptionAsync() { throw new NotImplementedException(); } async Task ThrowInvalidOperationExceptionAsync() { throw new InvalidOperationException(); }
3.4.1 仅捕捉其中的一个异常(推荐)
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 服务请求股票行情信息,只要有一个请求有响应任务就算完成。
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; }
五、等待全部任务完成,同时 其中任意一个任务完成时,实时处理相关结果
async Task<int> DelayAndReturnAsync(int value) { await Task.Delay(TimeSpan.FromSeconds(value)); return value; }
错误示范:
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); } }
正确示范:
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); }
六、当任务完成时,不需要恢复上下文
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 方法,这样在组织代码时,层次会更为清晰。