问题

执行若干个任务,只需要对其中任意一个的完成进行响应。这主要用于:对一个操作进行多种独立的尝试,只要一个尝试完成,任务就算完成。例如,同时向多个 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. 我和Python的Py交易》》》》》》数据类型

    Python里的变量 ---门牌 Python在使用变量之前无须定义它的类型,但是必须声明以及初始化该变量. Python中给变量赋值就是声明,初始化变量(也就是创建一个相应数据类型的对象,而那些数据 ...

  2. Django模板修炼

    引言:由于我们在使用Django框架时,不会将HTML代码采用硬编码的方式,因为会有以下缺点: 1:对页面设计进行的任何改变都必须对 Python 代码进行相应的修改. 站点设计的修改往往比底层 Py ...

  3. eShopOnContainers 知多少[9]:Ocelot gateways

    引言 客户端与微服务的通信问题永远是一个绕不开的问题,对于小型微服务应用,客户端与微服务可以使用直连的方式进行通信,但对于对于大型的微服务应用我们将不得不面对以下问题: 如何降低客户端到后台的请求数量 ...

  4. C# 高性能 TCP 服务的多种实现方式

    哎~~ 想想大部分园友应该对 "高性能" 字样更感兴趣,为了吸引眼球所以标题中一定要突出,其实我更喜欢的标题是<猴赛雷,C# 编写 TCP 服务的花样姿势!>. 本篇文 ...

  5. Spark学习之Spark SQL

    一.简介 Spark SQL 提供了以下三大功能. (1) Spark SQL 可以从各种结构化数据源(例如 JSON.Hive.Parquet 等)中读取数据. (2) Spark SQL 不仅支持 ...

  6. 工厂方法模式--java代码实现

    工厂方法模式 工厂方法模式,对简单工厂模式进行了升级.我们将水果园比作一个工厂,在简单工厂模式下,水果园是一个具体的工厂,直接用来生产各种各样的水果.那么在工厂方法模式下,水果园是一个抽象工厂,那么苹 ...

  7. Django-restframework 之认证源码分析

    Django-restframework 源码分析之认证 前言 最近学习了 django 的一个 restframework 框架,对于里面的执行流程产生了兴趣,经过昨天一晚上初步搞清楚了执行流程(部 ...

  8. 异步多线程 Async

    进程:进程是一个程序在电脑运行时,全部资源的合集叫进程 线程:是程序的最小执行单位,包含计算资源,任何一个操作的响应都是线程完成的.   多线程:多个线程并发执行   Thread 是.net框架封装 ...

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

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

  10. 衡量GDP,哪种夜间灯光数据更靠谱?

    <新科学家>杂志报道,随着经济发展,一些国家通常会新修道路,扩展居民区,这两项措施都会使从太空中看到的灯光强度增加.不少学者利用夜间灯光数据与国内生产总值统计数据进行比较,发现从太空中看到 ...