《C#并发编程经典实例》学习笔记—2.5 等待任意一个任务完成 Task.WhenAny
问题
执行若干个任务,只需要对其中任意一个的完成进行响应。这主要用于:对一个操作进行多种独立的尝试,只要一个尝试完成,任务就算完成。例如,同时向多个 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的更多相关文章
- 《C#并发编程经典实例》笔记
1.前言 2.开宗明义 3.开发原则和要点 (1)并发编程概述 (2)异步编程基础 (3)并行开发的基础 (4)测试技巧 (5)集合 (6)函数式OOP (7)同步 1.前言 最近趁着项目的一段平稳期 ...
- 《C#并发编程经典实例》学习笔记—2.7 避免上下文延续
避免上下文延续 在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 为了避免在上下文中恢复运行,可让 await 调用 ConfigureAwait 方法 ...
- 《C#并发编程经典实例》学习笔记—3.1 数据的并行处理
问题 有一批数据,需要对每个元素进行相同的操作.该操作是计算密集型的,需要耗费一定的时间. 解决方案 常见的操作可以粗略分为 计算密集型操作 和 IO密集型操作.计算密集型操作主要是依赖于CPU计算, ...
- 《C#并发编程经典实例》学习笔记—2.3 报告任务
问题 异步操作时,需要展示该操作的进度 解决方案 IProgress<T> Interface和Progress<T> Class 插一段话:读<C#并发编程经典实例&g ...
- 《C# 并发编程 · 经典实例》读书笔记
前言 最近在看<C# 并发编程 · 经典实例>这本书,这不是一本理论书,反而这是一本主要讲述怎么样更好的使用好目前 C#.NET 为我们提供的这些 API 的一本书,书中绝大部分是一些实例 ...
- [书籍]用UWP复习《C#并发编程经典实例》
1. 简介 C#并发编程经典实例 是一本关于使用C#进行并发编程的入门参考书,使用"问题-解决方案-讨论"的模式讲解了以下这些概念: 面向异步编程的async和await 使用TP ...
- C# 并发编程 · 经典实例
http://www.cnblogs.com/savorboard/p/csharp-concurrency-cookbook.html 异步基础 任务暂停,休眠 异步方式暂停或者休眠任务,可以使用 ...
- 《C#并发编程经典实例》学习笔记-关于并发编程的几个误解
误解一:并发就是多线程 实际上多线程只是并发编程的一种形式,在C#中还有很多更实用.更方便的并发编程技术,包括异步编程.并行编程.TPL 数据流.响应式编程等. 误解二:只有大型服务器程序才需要考虑并 ...
- 《C#并发编程经典实例》学习笔记-第一章并发编程概述
并发编程的术语 并发 同时做多件事情 多线程 并发的一种形式,它采用多个线程来执行程序. 多线程是并发的一种形式,但不是唯一的形式. 并行处理 把正在执行的大量的任务分割成小块,分配给多个同时运行的线 ...
随机推荐
- SQL Server事务 事务日志
事务 (SQL Server) 一.事务概念 事务是一种机制.是一种操作序列,它包含了一组数据库操作命令,这组命令要么全部执行,要么全部不执行.因此事务是一个不可分割的工作逻辑单元.在数据库系统 ...
- Linux 文件/文件夹无法删除问题解决方案
最近我们的服务器被黑客攻击,然后有些文件的属性被修改,导致我们无法删除病毒文件,同时采用 root 用户也无法删除,现在把解决方案记录下来. 普通删除 如果文件是当前用户的,那么使用 rm 命令就可以 ...
- Bootstrap优秀模板-ColorAdmin.4.3.0
老牌的Bootstrap商业模板,提供HTML.AngularJS.AngularJS5.Vue.React多种环境,风格覆盖Flat.Material.Apple.Transparent甚至Face ...
- final 关键字与安全发布 多线程中篇(十三)
final的通常理解 在Java中,final关键字可以用来修饰类.方法和变量(包括成员变量和局部变量) 大家应该都知道final表示最终的.最后的含义,也就是不能在继续 修饰类表示不能继承,修饰方法 ...
- MySQL 复制 - 性能与扩展性的基石 2:部署及其配置
正所谓理论造航母,现实小帆船.单有理论,不动手实践,学到的知识犹如空中楼阁.接下来,我们一起来看下如何一步步进行 MySQL Replication 的配置. 为 MySQL 服务器配置复制非常简单. ...
- .NET Core IdentityServer4实战-开篇介绍与规划
一.开篇寄语 由于假期的无聊,我决定了一个非常有挑战性的活动,也就是在年假给大家带来一个基于OAuth 2.0的身份授权框架,它就是 IdentityServer4 ,如果没有意外的话,一定可以顺利的 ...
- HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
题图:by @Olga Hi,大家好,我是承香墨影! HTTP 协议在网络知识中占据了重要的地位,HTTP 协议最基础的就是请求和响应的报文,而报文又是由报文头(Header)和实体组成.大多数 HT ...
- redis-win-server 正确启动方式
C:\Users\Administrator\Desktop\Redis-x64-2.8.2402\redis-server.exe C:\Users\Administrator\Desktop\R ...
- 使用 ASP.NET Core MVC 创建 Web API(五)
使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 使 ...
- [翻译 EF Core in Action 1.11] 何时不应该使用EF Core
Entity Framework Core in Action Entityframework Core in action是 Jon P smith 所著的关于Entityframework Cor ...