[C#.NET 拾遗补漏]16:几个常见的TAP异步操作

在本系列上一篇文章 [15:异步编程基础] 中,我们讲到,现代应用程序广泛使用的是基于任务的异步编程模式(TAP),历史的 EAP 和 AMP 模式已经过时不推荐使用。今天继续总结一下 TAP 的异步操作,比如取消任务、报告进度、Task.Yield()ConfigureAwait() 和并行操作等。

虽然实际 TAP 编程中很少使用到任务的状态,但它是很多 TAP 操作机理的基础,所以下面先从任务状态讲起。

1任务状态

Task 类为异步操作提供了一个生命周期,这个周期由 TaskStatus 枚举表示,它有如下值:

public enum TaskStatus
{Created = 0,WaitingForActivation = 1,WaitingToRun = 2,Running = 3,WaitingForChildrenToComplete = 4,RanToCompletion = 5,Canceled = 6,Faulted = 7
}

其中 CanceledFaultedRanToCompletion 状态一起被认为是任务的最终状态。因此,如果任务处于最终状态,则其 IsCompleted 属性为 true 值。

手动控制任务启动

为了支持手动控制任务启动,并支持构造与调用的分离,Task 类提供了一个 Start 方法。由 Task 构造函数创建的任务被称为冷任务,因为它们的生命周期处于 Created 状态,只有该实例的 Start 方法被调用才会启动。

任务状态平时用的情况不多,一般我们在封装一个任务相关的方法时,可能会用到。比如下面这个例子,需要判断某任务满足一定条件才启动:

static void Main(string[] args)
{MyTask t = new(() =>{// do something.});StartMyTask(t);Console.ReadKey();
}public static void StartMyTask(MyTask t)
{if (t.Status == TaskStatus.Created && t.Counter>10){t.Start();}else{// 这里模拟计数,直到 Counter>10 再执行 Startwhile (t.Counter <= 10){// Do somethingt.Counter++;}t.Start();}
}public class MyTask : Task
{public MyTask(Action action) : base(action){}public int Counter { get; set; }
}

同样,TaskStatus.Created 状态以外的状态,我们叫它热任务,热任务一定是被调用了 Start 方法激活过的。

确保任务已激活

注意,所有从 TAP 方法返回的任务都必须被激活,比如下面这样的代码:

MyTask task = new(() =>
{Console.WriteLine("Do something.");
});// 在其它地方调用
await task;

await 之前,任务没有执行 Task.Start 激活,await 时程序就会一直等待下去。所以如果一个 TAP 方法内部使用 Task 构造函数来实例化要返回的 Task,那么 TAP 方法必须在返回 Task 对象之前对其调用 Start

2任务取消

在 TAP 中,取消对于异步方法实现者和消费者来说都是可选的。如果一个操作允许取消,它就会暴露一个异步方法的重载,该方法接受一个取消令牌(CancellationToken 实例)。按照惯例,参数被命名为 cancellationToken。例如:

public Task ReadAsync(byte [] buffer, int offset, int count,CancellationToken cancellationToken)

异步操作会监控这个令牌是否有取消请求。如果收到取消请求,它可以选择取消操作,如下面的示例通过 while 来监控令牌的取消请求:

static void Main(string[] args)
{CancellationTokenSource source = new();CancellationToken token = source.Token;var task = DoWork(token);// 实际情况可能是在稍后的其它线程请求取消Thread.Sleep(100);source.Cancel();Console.WriteLine($"取消后任务返回的状态:{task.Status}");Console.ReadKey();
}public static Task DoWork(CancellationToken cancellationToken)
{while (!cancellationToken.IsCancellationRequested){// Do something.Thread.Sleep(1000);return Task.CompletedTask;}return Task.FromCanceled(cancellationToken);
}

如果取消请求导致工作提前结束,甚至还没有开始就收到请求取消,则 TAP 方法返回一个以 Canceled 状态结束的任务,它的 IsCompleted 属性为 true,且不会抛出异常。当任务在 Canceled 状态下完成时,任何在该任务注册的延续任务仍都会被调用和执行,除非指定了诸如 NotOnCanceled 这样的选项来选择不延续。

但是,如果在异步任务在工作时收到取消请求,异步操作也可以选择不立刻结束,而是等当前正在执行的工作完成后再结束,并返回 RanToCompletion 状态的任务;也可以终止当前工作并强制结束,根据实际业务情况和是否生产异常结果返回 CanceledFaulted 状态。

对于不能被取消的业务方法,不要提供接受取消令牌的重载,这有助于向调用者表明目标方法是否可以取消。

3进度报告

几乎所有异步操作都可以提供进度通知,这些通知通常用于用异步操作的进度信息更新用户界面。

在 TAP 中,进度是通过 IProgress<T> 接口来处理的,该接口作为一个参数传递给异步方法。下面是一个典型的的使用示例:

static void Main(string[] args)
{var progress = new Progress<int>(n =>{Console.WriteLine($"当前进度:{n}%");});var task = DoWork(progress);Console.ReadKey();
}public static async Task DoWork(IProgress<int> progress)
{for (int i = 1; i <= 100; i++){await Task.Delay(100);if (i % 10 == 0){progress?.Report(i);};}
}

输出如下结果:

当前进度:10%
当前进度:20%
当前进度:30%
当前进度:40%
当前进度:50%
当前进度:60%
当前进度:70%
当前进度:80%
当前进度:90%
当前进度:100%

IProgress<T> 接口支持不同的进度实现,这是由消费代码决定的。例如,消费代码可能只关心最新的进度更新,或者希望缓冲所有更新,或者希望为每个更新调用一个操作,等等。所有这些选项都可以通过使用该接口来实现,并根据特定消费者的需求进行定制。例如,如果本文前面的 ReadAsync 方法能够以当前读取的字节数的形式报告进度,那么进度回调可以是一个 IProgress<long> 接口。

public Task ReadAsync(byte[] buffer, int offset, int count,IProgress<long> progress)

再如 FindFilesAsync 方法返回符合特定搜索模式的所有文件列表,进度回调可以提供工作完成的百分比和当前部分结果集,它可以用一个元组来提供这个信息。

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(string pattern,IProgress<Tuple<double, ReadOnlyCollection<List<FileInfo>>>> progress)

或使用 API 特有的数据类型:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(string pattern,IProgress<FindFilesProgressInfo> progress)

如果 TAP 的实现提供了接受 IProgress<T> 参数的重载,它们必须允许参数为空,在这种情况下,不会报告进度。IProgress<T> 实例可以作为独立的对象,允许调用者决定如何以及在哪里处理这些进度信息。

4Task.Yield 让步

我们先来看一段 Task.Yield() 的代码:

Task.Run(async () =>
{for(int i=0; i<10; i++){await Task.Yield();...}
});

这里的 Task.Yield() 其实什么也没干,它返回的是一个空任务。那 await 一个什么也没做的空任务有什么用呢?

我们知道,对计算机来说,任务调度是根据一定的优先策略来安排线程去执行的。如果任务太多,线程不够用,任务就会进入排队状态。而 Yield 的作用就是让出等待的位置,让后面排除的任务先行。它字面上的意思就是让步,当任务做出让步时,其它任务就可以尽快被分配线程去执行。举个现实生活中的例子,就像你在排队办理业务时,好不容易到你了,但你的事情并不急,自愿让出位置,让其他人先办理,自己假装临时有事到外面溜一圈什么事也没干又回来重新排队。默默地做了一次大善人。

Task.Yield() 方法就是在异步方法中引入一个让步点。当代码执行到让步点时,就会让出控制权,去线程池外面兜一圈什么事也没干再回来重新排队。

5定制异步任务后续操作

我们可以对异步任务执行完成的后续操作进行定制。常见的两个方法是 ConfigureAwaitContinueWith

ConfigureAwait

我们先来看一段 Windows Form 中的代码:

private void button1_Click(object sender, EventArgs e)
{var content = CurlAsync().Result;...
}private async Task<string> CurlAsync()
{using (var client = new HttpClient()){return  await client.GetStringAsync("http://geekgist.com");}
}

想必大家都知道 CurlAsync().Result 这句代码在 Windows Form 程序中会造成死锁。原因是 UI 主线程执行到这句代码时,就开始等待异步任务的结果,处于阻塞状态。而异步任务执行完后回来准备找 UI 线程继续执行后面的代码时,却发现 UI 线程一直处于“忙碌”的状态,没空搭理回来的异步任务。这就造成了你等我,我又在等你的尴尬局面。

当然,这种死锁的情况只会在 Winform 和早期的 ASP.NET WebForm 中才会发生,在 Console 和 Web API 应用中不会生产死锁。

解决办法很简单,作为异步方法调用者,我们只需改用 await 即可:

private async void button1_Click(object sender, EventArgs e)
{var content = await CurlAsync();...
}

在异步方法内部,我们也可以调用任务的 ConfigureAwait(false) 方法来解决这个问题。如:

private async Task<string> CurlAsync()
{using (var client = new HttpClient()){return  await client.GetStringAsync("http://geekgist.com").ConfigureAwait(false);}
}

虽然两种方法都可行,但如果作为异步方法提供者,比如封装一个通用库时,考虑到难免会有新手开发者会使用 CurlAsync().Result,为了提高通用库的容错性,我们就可能需要使用 ConfigureAwait 来做兼容。

ConfigureAwait(false) 的作用是告诉主线程,我要去远行了,你去做其它事情吧,不用等我。只要先确保一方不在一直等另一方,就能避免互相等待而造成死锁的情况。

ContinueWith

ContinueWith 方法很容易理解,就是字面上的意思。作用是在异步任务执行完成后,安排后续要执行的工作。示例代码:

private void Button1_Click(object sender, EventArgs e)
{var backgroundScheduler = TaskScheduler.Default;var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();Task.Factory.StartNew(_ => DoBackgroundComputation(), backgroundScheduler).ContinueWith(_ => UpdateUI(), uiScheduler).ContinueWith(_ => DoAnotherBackgroundComputation(), backgroundScheduler).ContinueWith(_ => UpdateUIAgain(), uiScheduler);
}

如上,可以一直链式的写下去,任务会按照顺序执行,一个执行完再继续执行下一个。若其中一个任务返回的状态是 Canceled 时,后续的任务也将被取消。这个方法有好些个重载,在实际用到的时候再查看文档即可。

6总结

本文内容都是相对比较基础的 TAP 异步操作知识点。C# 的 TAP 很强大,提供的 API 也很多,远不止本文讲的这些,都是围绕 Task 转的。关键是要理解好基础操作,才能灵活使用更高级的功能。希望本文对你有所帮助。

参考:

https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap
https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/implementing-the-task-based-asynchronous-pattern
https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/consuming-the-task-based-asynchronous-pattern

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/303395.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

荐书 | 10本书给你计算机大师思维

《黑客与画家》作者&#xff1a;保罗格雷厄姆 / 译者&#xff1a;阮一峰美国互联网界如日中天的教父、哈佛大学离经叛道的博士保罗格雷厄姆著作&#xff01;一本书&#xff0c;你不曾想到的视角&#xff0c;彻底颠覆你对世界的认识&#xff01;美国亚马逊、纽约时报超级畅销书&…

如何解决U盘无法停止通用卷设备

当我们使用完U盘&#xff0c;在电脑上安全删除时&#xff0c;电脑却提示&#xff1a;无法停止‘通用卷设备。这是什么问题呢&#xff1f;这时候不要硬拔哦&#xff0c;因为硬拔可能会损坏我们U盘的资料文件或者电脑的USB接口。正确的解决方法&#xff1a; 第一种&#xff1a; 往…

php按时间分组的sql语句,(SQL语句)按指定时间段分组统计

---------------------------------------Author : liangCK 梁爱兰--Comment: 小梁 爱 兰儿--Date : 2010-01-02 16:47:10---------------------------------------> 生成测试数据: #tbCREATETABLE#tb(列名1varchar(12),时间datetime)INSERTINTO#tbSELECT03174190188,2009…

NET问答: 说说你对 LookupTKey, TElement 的看法 ?

咨询区 dan-gph&#xff1a;MSND 上对 Lookup 做了如下的解释。Lookup<TKey, TElement> 类似于 Dictionary<TKey,TValue>, 不同点在于 Dictionary<TKey, TValue> 中的key对应的是单个value&#xff0c;而 Lookup<TKey, TElement> 中的 key 对应的是一个…

最多金的编程语言Top10:Python第3,R第10,你猜第1是谁?

导读&#xff1a;如果你还在纠结选哪门编程语言来开启你的码农生涯&#xff0c;这将是你的最佳指南。本文绝非标题党&#xff0c;而是基于大量数据和为期两周的深入探究&#xff0c;在18年初&#xff0c;对IT行业的现状、趋势以及预测进行客观观察&#xff0c;让大家对编程语言…

[转贴]制作windows 2003自动安装盘-集成补丁/Raid及硬件驱动

从事网游行业的工作人员&#xff0c;如果一款游戏上线&#xff0c;必须上大量服务器。用品牌机引导盘装系统&#xff0c;再打个补丁&#xff0c;速度真让人不敢恭维。为了提高效率&#xff0c;就尝试制作一张集成系统补丁/RAID及硬件驱动自动安windows 2003系统盘。下面以品牌机…

4月 .NET 线上 Meetup,快来报名

点击蓝字关注我们.NET 6 preview 2 在3月11日已经发布&#xff0c;.NET 6 将是 .NET Core 3.1 之后的第一个 LTS 版本&#xff0c;也是微软开启全平台统一一个 .NET 计划以来的第一个 LTS 版本&#xff0c;意义不可谓不大&#xff0c;那么 .NET 5/6 又会带来哪些新特性呢&#…

NP完全性理论与近似算法

一、图灵机根据有限状态控制器的当前状态及每个读写头读到的带符号&#xff0c;图灵机的一个计算步可实现下面3个操作之一或全部。改变有限状态控制器中的状态。清除当前读写头下的方格中原有带符号并写上新的带符号。独立地将任何一个或所有读写头&#xff0c;向左移动一个方格…

php文件上传实验总结,53 PHP文件处理(六)文件上传--总结---细说php

前台表单设计,表单view.html,提交给upload.php一.表单view.htmlphp配置文件和上传文件有关选项&#xff0c;注意几点:php.inifile_uploadsonupload_max_filesize2M 此值最大不超过服务器内存upload_tmp_dirc:/uploads 到时要拷贝出来post_max_size25M 要大于upload_max_fil…

CentOS 5.6 快速搭建LAMP

在虚拟机下安装了个CentOS&#xff0c;5.6版本的。利用NAT模式与宿主pc共享上网&#xff01; 当然可以采用编译源码的方式搭建lamp。这里只是介绍快速搭建php开发框架的方法。其实也没什么&#xff0c;主要是是是使用linux的yum来在线安装。如果上不了网请查找编译lamp的文…

阿里25k 百度25k,招WPF!

.NET5打通7大开发方向&#xff0c;CLR超高性能&#xff0c;.NET6支持Blazor嵌入WPF&#xff0c;还有MAUI跨平台UI解决方案&#xff0c;都是.NET的利好&#xff0c;也是WPF的利好。牛年跳槽季&#xff0c;.NET在客户端方向一骑绝尘&#xff0c;阿里影视、百度地图&#xff0c;以…

php转译html,使用php转义输出HTML到JavaScript

最近在做天地图是GIS集成要输出HTML到JavaScript里面涉及到代码转义什么的比较麻烦所以写个PHP的function分享一下&#xff1a;function jsformat($str){$str trim($str);$str str_replace(\\s\\s, \\s, $str);$str str_replace(chr(10), , $str);$str str_replace(chr(13)…

掌握神经网络模型的快捷方式

TensorFlow是Google基于DistBelief进行研发的第二代人工智能学习系统&#xff0c;其命名来源于本身的运行原理。Tensor&#xff08;张量&#xff09;意味着N维数组&#xff0c;Flow&#xff08;流&#xff09;意味着基于数据流图的计算&#xff0c;TensorFlow实际上就是张量从流…

Visual Entity 手册(十一)代码生成设置

NHibernate 代码生成设置&#xff08;仅选取比较难理解的选项&#xff09; 一、DataContext 设置选项 1、Auto-Implemented Properties 设为 True 时&#xff0c;生成自动属性&#xff0c;如下&#xff1a; public partial class Category{public virtual int CategoryID { get…

.Net项目模板(Project Template)

你有没有这样的感觉&#xff0c;开启一个新项目时&#xff0c;总是做一堆体力活——项目的结构层次&#xff0c;常用日志库&#xff0c;OpenAPI库&#xff0c;ORM库&#xff0c;的引入&#xff0c;权限认证方式选择添加&#xff0c;配置文件重新归置存放等等。公共框架的部分总…

php导出数据库的指定表数据,MYSQL教程mysql数据库导出指定表数据的方法

《MYSQL教程mysql数据库导出指定表数据的方法》要点&#xff1a;本文介绍了MYSQL教程mysql数据库导出指定表数据的方法&#xff0c;希望对您有用。如果有疑问&#xff0c;可以联系我们。导读&#xff1a;linux下导出mysql中指定表数据 &#xff1a;MYSQLdump -uroot -p databas…

谈通过测试与失败测试

在软件测试技术中&#xff0c;现有的测试方法、测试技术中均未提及到通过测试与失败测试。 而在实际项目测试过程中&#xff0c;很多项目组却再应用该方法。虽然该方法被采用&#xff0c;但是很多人对通过测试与失败测试理解并不透侧&#xff0c;在实际使用过程中并未按统一的标…

100个微信小程序的源码公开分享

现在微信小程序越来越火&#xff0c;小编一直有意识地收集微信小程序源码&#xff0c;至今已经拥有100个小程序的源码&#xff0c;有gank、LOL战绩查询、百度小说、豆瓣电影、手势解锁等。现在&#xff0c;小编准备将这些资料免费分享给大家&#xff01;gankLOL战绩查询百度小说…

NET问答:什么场景下应该选择 struct 而不是 class ?

咨询区 Esteban Araya&#xff1a;MSDN 上说当你需要一个轻量级对象时应该选择 struct&#xff0c;说的含含糊糊&#xff0c;真的不知道有哪些场景下优先选择 struct 而不是 class。可能有些人已经忘了。struct 可以有方法。struct 不能被继承。我非常明白 struct 和 class 在技…

php类的的属性值,PHP面向对象之旅:类的属性

在PHP5中&#xff0c;在属性定义可以不设置初值&#xff0c;或者赋予以下红色类型的初值。PHP中简单类型有8种&#xff0c;分别是&#xff1a;数值类型boolean 布尔类型integer 整型float 浮点型&#xff0c;也称为 double 双精度浮点型string 字符串复合类型array 数组object …