异步编程基础——等待任意任务完成
问题
假设手头有若干项任务,需要响应其中完成的那一项。比如同事从多个Web服务请求股票行情信息,但是之关系第一个做出响应的服务
解决方案
使用Task.WhenAny方法。Task.WhenAny方法接受一个任务序列,当任意任务完成时,他就会返回已完成的任务,这时返回结果便是已经完成的任务。
//对于第一个要响应的URL,将其数据长度返回
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[] data=await completedTask;
return data.Length;
}
讨论
Task.WhenAny返回的任务从不会以错误状态或取消状态完成,这种”外部”任务总会成功完成,而它的返回值是第一个完成的Task(“内部”任务)。如果当内部任务完成时存在异常,那么该异常并不会传播至外部任务(Task.WhenAny返回的任务)。在内部任务完成后,应对其予以等待,确保观察到所有异常。
当第一个任务完成时,考虑是否取消剩下的任务。如果没有取消其他的任务,单页没有予以等待,那么他们便会被丢弃。被丢弃的任务会运行至完成,但程序会忽视其运行结果,同时会忽视这些任务中的所有异常。如果没有取消这些任务,那么它们自然会继续运行,白白地占用HTTP连接、数据库连接或计时器等资源。
使用Task.WhenAny可以实现超时(比如,将Task.Delay用作任务之一),但我并不推荐这样做。用取消来表达超时更合乎情理,而且取消可以带来增益,它可以切实地取消超时任务。
Task.WhenAny的另一个反模式是在任务完成时处理它们。留存一个任务列表并将完成任务从列表中逐个移除,咋看之下很合理,但这种方法的问题是,它执行了O(N^2)的时间,而其实可以采用O(N)算法