问题

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

文中举的是向多个Web服务询问股票价格的例子。

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

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

Task.WhenAny与Task.WhenAll比较:

  • 相同点:参数都是一批任务
  • 不同点: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,那么这些任务将被遗弃,被遗弃的任务并不是代表任务停止,而是任务继续执行直到完成,当然这些被遗弃的任务的结果或异常都会被忽略。

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

使用Task.WhenAny实现超时功能

书中给出的思路是其中一个任务是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.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;
}
}
}

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

  1. 《C#并发编程经典实例》笔记

    1.前言 2.开宗明义 3.开发原则和要点 (1)并发编程概述 (2)异步编程基础 (3)并行开发的基础 (4)测试技巧 (5)集合 (6)函数式OOP (7)同步 1.前言 最近趁着项目的一段平稳期 ...

  2. 《C#并发编程经典实例》学习笔记—2.7 避免上下文延续

    避免上下文延续 在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 为了避免在上下文中恢复运行,可让 await 调用 ConfigureAwait 方法 ...

  3. 《C#并发编程经典实例》学习笔记—3.1 数据的并行处理

    问题 有一批数据,需要对每个元素进行相同的操作.该操作是计算密集型的,需要耗费一定的时间. 解决方案 常见的操作可以粗略分为 计算密集型操作 和 IO密集型操作.计算密集型操作主要是依赖于CPU计算, ...

  4. 《C#并发编程经典实例》学习笔记—2.3 报告任务

    问题 异步操作时,需要展示该操作的进度 解决方案 IProgress<T> Interface和Progress<T> Class 插一段话:读<C#并发编程经典实例&g ...

  5. 《C# 并发编程 · 经典实例》读书笔记

    前言 最近在看<C# 并发编程 · 经典实例>这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例 ...

  6. [书籍]用UWP复习《C#并发编程经典实例》

    1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ...

  7. C# 并发编程 · 经典实例

    http://www.cnblogs.com/savorboard/p/csharp-concurrency-cookbook.html 异步基础 任务暂停,休眠 异步方式暂停或者休眠任务,可以使用  ...

  8. 《C#并发编程经典实例》学习笔记-关于并发编程的几个误解

    误解一:并发就是多线程 实际上多线程只是并发编程的一种形式,在C#中还有很多更实用.更方便的并发编程技术,包括异步编程.并行编程.TPL 数据流.响应式编程等. 误解二:只有大型服务器程序才需要考虑并 ...

  9. 《C#并发编程经典实例》学习笔记-第一章并发编程概述

    并发编程的术语 并发 同时做多件事情 多线程 并发的一种形式,它采用多个线程来执行程序. 多线程是并发的一种形式,但不是唯一的形式. 并行处理 把正在执行的大量的任务分割成小块,分配给多个同时运行的线 ...

随机推荐

  1. 对于单页应用中如何监听 URL 变化的思考

    周末开发了一个在 GitHub 中给 repo 增加自定义备注的 chrome 扩展. 开发这个扩展的原因是我在 GitHub 中所 star 的项目实在太多了(截止目前 671 个),有的项目过个几 ...

  2. Protobuf 语言指南(proto3)

    Protobuf 语言指南(proto3) Protocol Buffer是Google的语言中立的,平台中立的,可扩展机制的,用于序列化结构化数据 - 对比XML,但更小,更快,更简单.您可以定义数 ...

  3. mysql开发规范(优化)

    规范 库名.表名.字段名必须使用小写字母, 并采用下划线分割, 禁止超过32个字符(整齐.易读) 临时库.表名须以tmp加日期为后缀; 使用Innodb存储引擎.[好处: 支持事务和行级锁] 字符集统 ...

  4. 《前端之路》之 Babel 下一代 JavaScript 语法编译器

    写本章的内容的出发点主要是 为了对于之前关于 JS 版本的一个总结,在之前的开发中,我们始终对于 ECMAScript 的版本的更新不够重视,以至于在后面的 开发过程中,我们始终会被各种新奇的语法打断 ...

  5. ShortcutBadgerDemo【安卓应用角标(badge)实现方案】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 本文主要使用的开源库是 leolin310148/ShortcutBadger,但是在其基础上做了一些修改. 什么是应用角标? 1. ...

  6. NIO(生活篇)

    今晚是个下雨天,写完今天最后一行代码,小鲁班起身合上电脑,用滚烫的开水为自己泡制了一桶老坛酸菜牛肉面.这大概是苦逼程序猿给接下来继续奋战的自己最好的馈赠.年轻的程序猿更偏爱坐在窗前,在夜晚中静静的享受 ...

  7. mqtt服务器apollo的搭建和测试工具paho的使用

    (1)前言 MQTT协议是IBM开发的一个即时通讯协议; 基于发布/订阅的消息协议,近些年来被广泛应用于能源.电力.....等硬件性能低下的远程设备,此外国内很多企业使用MQTT作为android手机 ...

  8. Springcloud Gateway 路由管理

    Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开 ...

  9. uni-app—从安装到卸载

    uni-app实现了一套代码,同时运行到多个平台.支持iOS模拟器.Android模拟器.H5.微信开发者工具.支付宝小程序Studio.百度开发者工具.字节跳动开发者工具 工具安装 开发uni-ap ...

  10. c# 创建Excel com加载项图片对象批量操作

    技术含量较低,主要是通过VBA代码转换成c#代码而来,从而实现图片批量插入.删除.另存为的批量操作,增加文档使用的通用性. 插件主要界面如下: 主要代码如下: private void button8 ...