问题

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

    在我刚刚开通博客园后,想了好久.不知道第一次随笔该写点什么,想写些自己学到的知识,但技术上还是菜鸟的我完全不知道我能向别人分享什么,想到这里有些沮丧. 但万事开头难,只要我入了门,广阔的编程语言的世界 ...

  2. H5单张、多张图片上传

    前言 今天我们聊一聊图片上传,单张Or多张 ,如今,各大图片上传插件数不胜数,例如:Jquery的 verupload.js,jQuery File Upload.Uploadify.jQuery.f ...

  3. python设计模式-观察者

    定义: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖的对象都会得到通知并被自动更新. 观察者模式是对象的行为模式,又叫发布-订阅(pubish/subscribe)模式,模型 ...

  4. 神奇的Scala Macro之旅(二)- 一个实例

    优化的日志方式 package macros_demo import scala.language.experimental.macrosimport org.slf4j._import scala. ...

  5. 操作系统底层原理与Python中socket解读

    目录 操作系统底层原理 网络通信原理 网络基础架构 局域网与交换机/网络常见术语 OSI七层协议 TCP/IP五层模型讲解 Python中Socket模块解读 TCP协议和UDP协议 操作系统底层原理 ...

  6. jdk源码阅读笔记-LinkedList

    一.LinkedList概述 LinkedList的底层数据结构为双向链表结构,与ArrayList相同的是LinkedList也可以存储相同或null的元素.相对于ArrayList来说,Linke ...

  7. .NET Core Dapper操作mysql数据库

    前言 现在ORM盛行,市面上已经出现了N款不同的ORM套餐了.今天,我们不谈EF,也不聊神马黑马,就说说 Dapper.如何在.NET Core中使用Dapper操作Mysql数据库呢,让我们跟随镜头 ...

  8. windows 重写调试输出

    // 使用OutputDebugString很不方便.不能自定义格式化输出.所以重写了一下. #include <tchar.h> #include <windows.h> v ...

  9. .net mvc + layui做图片上传(二)—— 使用流上传和下载图片

    摘要:上篇文章写到一种上传图片的方法,其中提到那种方法的局限性,就是上传的文件只能保存在本项目目录下,在其他目录中访问不到该文件.这与浏览器的安全性机制有关,浏览器不允许用户用任意的路径访问服务器上的 ...

  10. Rest_framework Router 路由器(含SimplyRouter源码浅解)

    目录 Rest_framework Router 路由器 ViewSet结合Router,自动生成url. 将ViewSet注册到Router中,需要三个要素: 关于路由规则,细分有四类: rest_ ...