《C#并发编程经典实例》学习笔记—2.5 等待任意一个任务完成 Task.WhenAny

问题

执行若干个任务,只需要对其中任意一个的完成进行响应。这主要用于:对一个操作进行多种独立的尝试,只要一个尝试完成,任务就算完成。例如,同时向多个 Web 服务询问股票价格,但是只关心第一个响应的。

文中举的是向多个Web服务询问股票价格的例子。
我曾在过往的工作中遇到另一个不太相似的例子。一个问答项目,在问题详情页面,重要的是问题展示和回答展示。在该页面有相关房型推荐和类似问题推荐等等多个模块展示。也就是说在请求问题数据之外还需要请求多个接口,按理说这个时候最适合的是使用Task.WhenAll,但是?#31508;?#24773;形下因为服务器?#38405;?#21463;限导致页面加载过慢影响用户访问,所以其时最快需要解决的是页面加载过慢的问题,所以这?#31508;?#29992;Task.WhenAny或许也算得上是一个应急折中的方案,当然这里不提缓存等其他优化方案。

首先查看官方文档,了解所有重载和返回值:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task.whenany?view=netcore-2.2

Task.WhenAny与Task.WhenAll比较:

  • 相同点:?#38382;?#37117;是一批任务
  • 不同点:Task.WhenAny返回的是完成的任务。

关于返回值的描述有点不太好理解。结合代码很容易就能明白。

// 返回第一个响应的 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,那么这些任务将被遗弃,被遗弃的任务并不是代表任务停止,而是任务继续执行直到完成,当然这些被遗弃的任务的结果或异常都会?#32531;?#30053;。

文中提到了另外两个对WhenAny的使用方法。我试着写了demo。

使用Task.WhenAny实现超时功能

书中给出的思路是其中一个任务是Delay的,这样返回的第一个任务如果是该Delay任务。文中不推荐该方式,因为没有取消功能,即其他超时的任务不能被取消,无疑对计算机?#35797;?#26159;一种浪费,对?#38405;芤不?#36896;成影响。

        // 返回第一个响应的 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.WhenAny处理已完成的任务

书中给出的思路是,列表存放Task,完成一个任务就移除一个已完成的Task。文中不推荐此方法,因为执行时间是 O(N^2),2.6小节有 O(N) 的算法。

        // 处理已完成的任务
        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;
                }
            }
        }
posted @ 2019-03-27 17:49 repeatedly 阅读(...) 评论(...) 编辑 收藏
耐克篮球多少钱
天津十一选五开奖结果 南粤1753期七星彩规律 宁夏11选5今日开奖走势 a6国际娱乐平台 辽宁快乐12选五走势图表 香港曾氏原创二肖中特 河南快赢481开奖视频 福利彩票35选7走大势 内蒙古快3定位走势图 新疆喜乐彩中奖详情 篮球比赛积分表 四川金7乐64期开奖结果 极速快3开奖结果查询 看图选特码 天津11选5遗漏