问题

执行若干个任务,只需要对其中任意一个的完成进行响应。这主要用于:对一个操作进行多种独立的尝试,只要一个尝试完成,任务就算完成。例如,同时向多个 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返回的是完成的任务。

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

  1. // 返回第一个响应的 URL 的数据长度。
  2. private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB)
  3. {
  4. var httpClient = new HttpClient();
  5. // 并发地开始两个下载任务。
  6. Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
  7. Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
  8. // 等待任意一个任务完成。
  9. Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
  10. // 返回从 URL 得到的数据的长度。
  11. byte[] data = await completedTask;
  12. return data.Length;
  13. }

注意 Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);,使用await获取返回结果仍然是一个Task任务,如果Task.WhenAny返回的Task有异常,这行代码并不会抛出异常,而是在byte[] data = await completedTask; 这里抛出异常。

在第一个任务完成之后,如果其他任务没有被取消,也不曾await,那么这些任务将被遗弃,被遗弃的任务并不是代表任务停止,而是任务继续执行直到完成,当然这些被遗弃的任务的结果或异常都会被忽略。

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

使用Task.WhenAny实现超时功能

书中给出的思路是其中一个任务是Delay的,这样返回的第一个任务如果是该Delay任务。文中不推荐该方式,因为没有取消功能,即其他超时的任务不能被取消,无疑对计算机资源是一种浪费,对性能也会造成影响。

  1. // 返回第一个响应的 URL 的数据长度。
  2. private static async Task<int> FirstRespondingUrlAsync(string urlA, string urlB)
  3. {
  4. var httpClient = new HttpClient();
  5. // 并发地开始两个下载任务。
  6. Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
  7. Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
  8. Task<byte[]> delayTask = GetDelayTask();
  9. // 等待任意一个任务完成。
  10. Task<byte[]> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB, delayTask);
  11. // 返回从 URL 得到的数据的长度。
  12. byte[] data = await completedTask;
  13. if (data.Length == 1 && data[0] == byte.MaxValue)
  14. {
  15. Console.WriteLine("超时提醒");
  16. }
  17. return data.Length;
  18. }
  19. // 获取超时任务
  20. private static Task<byte[]> GetDelayTask()
  21. {
  22. return new Task<byte[]>(() =>
  23. {
  24. Task.Delay(1000);
  25. return new[] { byte.MaxValue };
  26. });
  27. }

使用Task.WhenAny处理已完成的任务

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

  1. // 处理已完成的任务
  2. private static async Task ProcessTasksAsync(string urlA, string urlB)
  3. {
  4. var httpClient = new HttpClient();
  5. // 并发地开始两个下载任务。
  6. Task<byte[]> downloadTaskA = httpClient.GetByteArrayAsync(urlA);
  7. Task<byte[]> downloadTaskB = httpClient.GetByteArrayAsync(urlB);
  8. var tasks = new List<Task<byte[]>> { downloadTaskA, downloadTaskB };
  9. while (true)
  10. {
  11. // 等待任意一个任务完成。
  12. Task<byte[]> completedTask = await Task.WhenAny(tasks);
  13. //移除已完成的任务
  14. tasks.Remove(completedTask);
  15. if (!tasks.Any())
  16. {
  17. break;
  18. }
  19. }
  20. }

《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. ffmpeg 获得视频的时间长度, 仅仅学习一下

    public static void main(String[] args) { String result = processFLV("E:\\test\\京视传媒\\体育类\\xiao. ...

  2. uiautomator——简单的将自动化测试与截图对比相结合使用的小例子!

    1.在使用uiautomator进行自动化测试过程当中,执行某些动作之后,要进行判断是否已执行的动作成功时,需要添加检查点来判断,因此添加判断点除了使用id.text.view等方式进行之外,存在一种 ...

  3. 二分搜索树实现Java的Map(下)

    二分搜索树Map public class BSTMap<K extends Comparable<K>,V> implements Map<K,V> { priv ...

  4. 二分法与二叉树的 Java 实现

    算法与数据结构始终是计算机基础的重要一环,今天我们来讨论下 Java 中二叉树的实现以及一些简单的小算法,如二分查找,归并排序等. 二分查找 二分查找是一种在有序数组中查找某一特定元素的搜索算法,它在 ...

  5. 『OGG 01』Win7 配置 Oracle GoldenGate 踩坑指南

    安装 Oracle 安装 Oracle11g 32位[Oracle 32位的话,OGG 也必须是 32位,否则会有0xc000007b无法正常启动 错误] 安装目录为 D:\oracle\produc ...

  6. Java进阶篇设计模式之十三 ---- 观察者模式和空对象模式

    前言 在上一篇中我们学习了行为型模式的备忘录模式(Memento Pattern)和状态模式(Memento Pattern).本篇则来学习下行为型模式的最后两个模式,观察者模式(Observer P ...

  7. Git协同工作流介绍

    git相关的文章和教程非常多,但是系统介绍和了解工作流的人并不多,在使用过程中用错或用偏的也不少,这里分享的是,假设你已经入门的情况下,我们如何去选择适合团队需要的工作流. git优势 这里先唠叨gi ...

  8. Python中使用枚举类

    开发中我们经常定义常量, 其实有更好的方法:为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例.Python中提供了Enum类来实现这个功能: from enum im ...

  9. openlayers4 入门开发系列之热力图篇(附源码下载)

    前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...

  10. sql server replace的替换字符,replace的使用

    sql server replace的替换字符,replace的使用 select REPLACE(name,'张','') * from entity_5c7a578c05c7042958d9148 ...