异步编程基础
使用 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 方法,这样在组织代码时,层次会更为清晰。
浙公网安备 33010602011771号