执行若干个任务,只须要对其中任意一个的完成进行响应。这主要用于:对一个操做进行多种独立的尝试,只要一个尝试完成,任务就算完成。例如,同时向多个 Web 服务询问股票价格,可是只关心第一个响应的。算法
文中举的是向多个Web服务询问股票价格的例子。
我曾在过往的工做中遇到另外一个不太类似的例子。一个问答项目,在问题详情页面,重要的是问题展现和回答展现。在该页面有相关房型推荐和相似问题推荐等等多个模块展现。也就是说在请求问题数据以外还须要请求多个接口,按理说这个时候最适合的是使用Task.WhenAll,可是当时情形下由于服务器性能受限致使页面加载过慢影响用户访问,因此其时最快须要解决的是页面加载过慢的问题,因此这时使用Task.WhenAny或许也算得上是一个应急折中的方案,固然这里不提缓存等其余优化方案。api
首先查看官方文档,了解全部重载和返回值:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task.whenany?view=netcore-2.2缓存
Task.WhenAny与Task.WhenAll比较:服务器
关于返回值的描述有点不太好理解。结合代码很容易就能明白。并发
// 返回第一个响应的 URL 的数据长度。 private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB) { var httpClient = new HttpClient(); // 并发地开始两个下载任务。 Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA); Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB); // 等待任意一个任务完成。 Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB); // 返回从 URL 获得的数据的长度。 byte[] data = await completedTask; return data.Length; }
注意Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
,使用await
获取返回结果仍然是一个Task任务,若是Task.WhenAny返回的Task有异常,这行代码并不会抛出异常,而是在byte[] data = await completedTask;
这里抛出异常。
在第一个任务完成以后,若是其余任务没有被取消,也未曾await,那么这些任务将被遗弃,被遗弃的任务并非表明任务中止,而是任务继续执行直到完成,固然这些被遗弃的任务的结果或异常都会被忽略。async
文中提到了另外两个对WhenAny的使用方法。我试着写了demo。性能
书中给出的思路是其中一个任务是Delay的,这样返回的第一个任务若是是该Delay任务。文中不推荐该方式,由于没有取消功能,即其余超时的任务不能被取消,无疑对计算机资源是一种浪费,对性能也会形成影响。优化
// 返回第一个响应的 URL 的数据长度。 private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB) { var httpClient = new HttpClient(); // 并发地开始两个下载任务。 Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA); Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB); Task<byte[]> delayTask = GetDelayTask(); // 等待任意一个任务完成。 Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB, delayTask); // 返回从 URL 获得的数据的长度。 byte[] data = await completedTask; if (data.Length == 1 && data[0] == byte.MaxValue) { Console.WriteLine("超时提醒"); } return data.Length; } // 获取超时任务 private static Task<byte[]> GetDelayTask() { return new Task<byte[]>(() => { Task.Delay(1000); return new[] { byte.MaxValue }; }); }
书中给出的思路是,列表存放Task,完成一个任务就移除一个已完成的Task。文中不推荐此方法,由于执行时间是 O(N^2),2.6小节有 O(N) 的算法。url
// 处理已完成的任务 private static async Task ProcessTasksAsync(string urlA, string urlB) { var httpClient = new HttpClient(); // 并发地开始两个下载任务。 Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA); Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB); var tasks = new List<Task<byte[]>> { downloadTaskA, downloadTaskB }; while (true) { // 等待任意一个任务完成。 Task<byte[]> completedTask = await Task.WhenAny(tasks); //移除已完成的任务 tasks.Remove(completedTask); if (!tasks.Any()) { break; } } }