【转】Task和async/await详解

一、什么是异步

  同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。

  异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,让异步编程更为方便。本篇主要介绍Task、async/await相关的内容,其他异步操作的方式会在下一篇介绍。

回到顶部

二、Task介绍

  Task是在ThreadPool的基础上推出的,我们简单了解下ThreadPool。ThreadPool中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务,如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。线程池能减少线程的创建,节省开销,看一个ThreadPool的栗子吧

复制代码

复制代码

        static void Main(string[] args){for (int i = 1; i <=10; i++){//ThreadPool执行任务ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => {Console.WriteLine($"第{obj}个执行任务");}),i);}Console.ReadKey();}

复制代码

复制代码

  上边的代码通过ThreadPool执行了10个任务,执行结果为:

   ThreadPool相对于Thread来说可以减少线程的创建,有效减小系统开销;但是ThreadPool不能控制线程的执行顺序,我们也不能获取线程池内线程取消/异常/完成的通知,即我们不能有效监控和控制线程池中的线程。

1 Task创建和运行

  我们知道了ThreadPool的弊端:我们不能控制线程池中线程的执行顺序,也不能获取线程池内线程取消/异常/完成的通知。net4.0在ThreadPool的基础上推出了Task,Task拥有线程池的优点,同时也解决了使用线程池不易控制的弊端。

首先看一下怎么去创建并运行一个Task,Task的创建和执行方式有如下三种:

复制代码

复制代码

        static void Main(string[] args){//1.new方式实例化一个Task,需要通过Start方法启动Task task = new Task(() =>{Thread.Sleep(100);Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");});task.Start();//2.Task.Factory.StartNew(Action action)创建和启动一个TaskTask task2 = Task.Factory.StartNew(() =>{Thread.Sleep(100);Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");});//3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个TaskTask task3 = Task.Run(() =>{Thread.Sleep(100);Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");});Console.WriteLine("执行主线程!");Console.ReadKey();}

复制代码

复制代码

执行结果如下:

  我们看到先打印"执行主线程",然后再打印各个任务,说明了Task不会阻塞主线程。上边的栗子Task都没有返回值,我们也可以创建有返回值的Task<TResult>,用法和没有返回值的基本一致,我们简单修改一下上边的栗子,代码如下:

复制代码

复制代码

        static void Main(string[] args){1.new方式实例化一个Task,需要通过Start方法启动Task<string> task = new Task<string>(() =>{return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";});task.Start();2.Task.Factory.StartNew(Func func)创建和启动一个TaskTask<string> task2 =Task.Factory.StartNew<string>(() =>{return $"hello, task2的ID为{ Thread.CurrentThread.ManagedThreadId}";});3.Task.Run(Func func)将任务放在线程池队列,返回并启动一个TaskTask<string> task3= Task.Run<string>(() =>{return $"hello, task3的ID为{ Thread.CurrentThread.ManagedThreadId}";});Console.WriteLine("执行主线程!");Console.WriteLine(task.Result);Console.WriteLine(task2.Result);Console.WriteLine(task3.Result);Console.ReadKey();}

复制代码

复制代码

  注意task.Resut获取结果时会阻塞线程,即如果task没有执行完成,会等待task执行完成获取到Result,然后再执行后边的代码,程序运行结果如下:   

  上边的所有栗子中Task的执行都是异步的,不会阻塞主线程。有些场景下我们想让Task同步执行怎么办呢?Task提供了  task.RunSynchronously()用于同步执行Task任务,代码如下:

复制代码

复制代码

        static void Main(string[] args){Task task = new Task(() =>{Thread.Sleep(100);Console.WriteLine("执行Task结束!");});//同步执行,task会阻塞主线程task.RunSynchronously();Console.WriteLine("执行主线程结束!");Console.ReadKey();}

复制代码

复制代码

执行结果如下:

 2 Task的阻塞方法(Wait/WaitAll/WaitAny)

1 Thread阻塞线程的方法

  使用Thread时,我们知道用thread.Join()方法即可阻塞主线程。看一个例子:

复制代码

复制代码

        static void Main(string[] args){Thread th1 = new Thread(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});th1.Start();Thread th2 = new Thread(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});th2.Start();//阻塞主线程th1.Join();th2.Join();Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

复制代码

复制代码

  如果注释掉两个Join,执行结果是:先打印【主线程执行完毕】,而添加两个Join方法后执行结果如下,实现了线程阻塞:

2 Task的Wait/WaitAny/WaitAll方法

   Thread的Join方法可以阻塞调用线程,但是有一些弊端:①如果我们要实现很多线程的阻塞时,每个线程都要调用一次Join方法;②如果我们想让所有的线程执行完毕(或者任一线程执行完毕)时,立即解除阻塞,使用Join方法不容易实现。Task提供了  Wait/WaitAny/WaitAll  方法,可以更方便地控制线程阻塞。

  task.Wait()  表示等待task执行完毕,功能类似于thead.Join();  Task.WaitAll(Task[] tasks)  表示只有所有的task都执行完成了再解除阻塞;  Task.WaitAny(Task[] tasks) 表示只要有一个task执行完毕就解除阻塞,看一个栗子: 

复制代码

复制代码

        static void Main(string[] args){Task task1 = new Task(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});task1.Start();Task task2 = new Task(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});task2.Start();//阻塞主线程。task1,task2都执行完毕再执行主线程//执行【task1.Wait();task2.Wait();】可以实现相同功能Task.WaitAll(new Task[]{ task1,task2});Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

复制代码

复制代码

  执行结果如下:

  如果将栗子中的WaitAll换成WaitAny,那么任一task执行完毕就会解除线程阻塞,执行结果是:先打印【线程1执行完毕】,然后打印【主线程执行完毕】,最后打印【线程2执行完毕】

 3 Task的延续操作(WhenAny/WhenAll/ContinueWith)

  上边的Wait/WaitAny/WaitAll方法返回值为void,这些方法单纯的实现阻塞线程。我们现在想让所有task执行完毕(或者任一task执行完毕)后,开始执行后续操作,怎么实现呢?这时就可以用到WhenAny/WhenAll方法了,这些方法执行完成返回一个task实例。  task.WhenAll(Task[] tasks)  表示所有的task都执行完毕后再去执行后续的操作, task.WhenAny(Task[] tasks)  表示任一task执行完毕后就开始执行后续操作。看一个栗子:

复制代码

复制代码

        static void Main(string[] args){Task task1 = new Task(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});task1.Start();Task task2 = new Task(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});task2.Start();//task1,task2执行完了后执行后续操作Task.WhenAll(task1, task2).ContinueWith((t) => {Thread.Sleep(100);Console.WriteLine("执行后续操作完毕!");});Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

复制代码

复制代码

  执行结果如下,我们看到WhenAll/WhenAny方法不会阻塞主线程,当使用WhenAll方法时所有的task都执行完毕才会执行后续操作;如果把栗子中的WhenAll替换成WhenAny,则只要有一个线程执行完毕就会开始执行后续操作,这里不再演示。

   上边的栗子也可以通过 Task.Factory.ContinueWhenAll(Task[] tasks, Action continuationAction)  和 Task.Factory.ContinueWhenAny(Task[] tasks, Action continuationAction) 来实现 ,修改上边栗子代码如下,执行结果不变。

复制代码

复制代码

       static void Main(string[] args){Task task1 = new Task(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});task1.Start();Task task2 = new Task(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});task2.Start();//通过TaskFactroy实现Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>{Thread.Sleep(100);Console.WriteLine("执行后续操作");});Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

复制代码

复制代码

 4 Task的任务取消(CancellationTokenSource)

1 Thread取消任务执行

  在Task前我们执行任务采用的是Thread,Thread怎么取消任务呢?一般流程是:设置一个变量来控制任务是否停止,如设置一个变量isStop,然后线程轮询查看isStop,如果isStop为true就停止,代码如下:

复制代码

复制代码

        static void Main(string[] args){bool isStop = false;int index = 0;//开启一个线程执行任务Thread th1 = new Thread(() =>{while (!isStop){Thread.Sleep(1000);Console.WriteLine($"第{++index}次执行,线程运行中...");}});th1.Start();//五秒后取消任务执行Thread.Sleep(5000);isStop = true;Console.ReadKey();}

复制代码

复制代码

2 Task取消任务执行

  Task中有一个专门的类 CancellationTokenSource  来取消任务执行,还是使用上边的例子,我们修改代码如下,程序运行的效果不变。

复制代码

复制代码

        static void Main(string[] args){CancellationTokenSource source = new CancellationTokenSource();int index = 0;//开启一个task执行任务Task task1 = new Task(() =>{while (!source.IsCancellationRequested){Thread.Sleep(1000);Console.WriteLine($"第{++index}次执行,线程运行中...");}});task1.Start();//五秒后取消任务执行Thread.Sleep(5000);//source.Cancel()方法请求取消任务,IsCancellationRequested会变成truesource.Cancel();Console.ReadKey();}

复制代码

复制代码

   CancellationTokenSource的功能不仅仅是取消任务执行,我们可以使用  source.CancelAfter(5000)  实现5秒后自动取消任务,也可以通过  source.Token.Register(Action action)  注册取消任务触发的回调函数,即任务被取消时注册的action会被执行。 看一个栗子:

复制代码

复制代码

        static void Main(string[] args){CancellationTokenSource source = new CancellationTokenSource();//注册任务取消的事件source.Token.Register(() =>{Console.WriteLine("任务被取消后执行xx操作!");});int index = 0;//开启一个task执行任务Task task1 = new Task(() =>{while (!source.IsCancellationRequested){Thread.Sleep(1000);Console.WriteLine($"第{++index}次执行,线程运行中...");}});task1.Start();//延时取消,效果等同于Thread.Sleep(5000);source.Cancel();source.CancelAfter(5000);Console.ReadKey();}

复制代码

复制代码

  执行结果如下,第5次执行在取消回调后打印,这是因为,执行取消的时候第5次任务已经通过了while()判断,任务已经执行中了:

   最后看上一篇跨线程的栗子,点击按钮启动一个任务,给tetxtbox赋值,我们把Thread改成Task,代码如下:

复制代码

复制代码

    public partial class Form1 : Form{public Form1(){InitializeComponent();}private void mySetValueBtn_Click(object sender, EventArgs e){Task.Run(() =>{Action<int> setValue = (i) => { myTxtbox.Text = i.ToString(); };for (int i = 0; i < 1000000; i++){myTxtbox.Invoke(setValue,i);}});}}

复制代码

复制代码

  运行界面如下,赋值的task不会阻塞UI线程:

回到顶部

三、异步方法(async/await)

1.简单认识

  在C#5.0中出现的 async和await ,让异步编程变得更简单。我们看一个获取文件内容的栗子:

复制代码

复制代码

    class Program{static void Main(string[] args){string content = GetContentAsync(Environment.CurrentDirectory + @"/test.txt").Result;//调用同步方法//string content = GetContent(Environment.CurrentDirectory + @"/test.txt");Console.WriteLine(content);Console.ReadKey();}//异步读取文件内容async static Task<string> GetContentAsync(string filename){FileStream fs = new FileStream(filename, FileMode.Open);var bytes = new byte[fs.Length];//ReadAync方法异步读取内容,不阻塞线程Console.WriteLine("开始读取文件");int len = await fs.ReadAsync(bytes, 0, bytes.Length);string result = Encoding.UTF8.GetString(bytes);return result;}//同步读取文件内容static string GetContent(string filename){FileStream fs = new FileStream(filename, FileMode.Open);var bytes = new byte[fs.Length];//Read方法同步读取内容,阻塞线程int len =  fs.Read(bytes, 0, bytes.Length);string result = Encoding.UTF8.GetString(bytes);return result;}}

复制代码

复制代码

  test.txt内容是【hello world!】执行结果为:

  上边的栗子也写出了同步读取的方式,将main函数中的注释去掉即可同步读取文件内容。我们可以看到异步读取代码和同步读取代码基本一致。async/await让异步编码变得更简单,我们可以像写同步代码一样去写异步代码。注意一个小问题:异步方法中方法签名返回值为Task<T>,代码中的返回值为T。上边栗子中GetContentAsync的签名返回值为Task<string>,而代码中返回值为string。牢记这一细节对我们分析异步代码很有帮助。

  异步方法签名的返回值有以下三种:

    ① Task<T>:如果调用方法想通过调用异步方法获取一个T类型的返回值,那么签名必须为Task<TResult>;

    ② Task:如果调用方法不想通过异步方法获取一个值,仅仅想追踪异步方法的执行状态,那么我们可以设置异步方法签名的返回值为Task;

    ③ void:如果调用方法仅仅只是调用一下异步方法,不和异步方法做其他交互,我们可以设置异步方法签名的返回值为void,这种形式也叫做“调用并忘记”。

  小结:到这里Task,async/await的简单使用已经基本结束了,一些高级特性等到工作遇到了再去研究。通过上边的介绍,我们知道async/await是基于Task的,而Task是对ThreadPool的封装改进,主要是为了更有效的控制线程池中的线程(ThreadPool中的线程,我们很难通过代码控制其执行顺序,任务延续和取消等等);ThreadPool基于Thread的,主要目的是减少Thread创建数量和管理Thread的成本。async/await Task是C#中更先进的,也是微软大力推广的特性,我们在开发中可以尝试使用Task来替代Thread/ThreadPool,处理本地IO和网络IO任务是尽量使用async/await来提高任务执行效率。

好文要顶 关注我 收藏该文  

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

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

相关文章

LeetCode每日打卡 - 反转每对括号间的子串

题解中有个更好的思路&#xff0c;stack存入的不是(的坐标&#xff0c;存入每次到左括号的字符串&#xff0c;拿到右括号就对其进行反转&#xff0c;更妙一些。 import java.util.Stack; class Solution {public String reverseParentheses(String s) {Stack<Integer> s…

iar stm32_树莓派玩转STM32开发(一)——介绍篇

01—树莓派树莓派(Raspberry Pi)听起来让人流口水&#xff0c;但它的确不是吃的(身为非吃货的我第一次也以为它是食物……)。树莓派是基于ARM架构的Linux卡片电脑&#xff0c;由英国树莓派基金会开发&#xff0c;目的是以低价硬件以及自由软件来促进学校的基本电脑科学教育。树…

JavaScript面向对象的理解

前言 1. 本文默认阅读者已有面向对象的开发思想&#xff0c;最好是使用过c、java&#xff0c;本人Java不太熟悉&#xff0c;所以例子都是用C来写的。 2. 本人不是专业网站开发人员&#xff0c;接触javascript一年多&#xff0c;自己也编写调试了一些代码&#xff0c;本文完全根…

【转】C# 彻底搞懂async/await

关键&#xff1a; 异步方法&#xff1a;在执行完成前立即返回调用方法&#xff0c;在调用方法继续执行的过程中完成任务。 async/await 结构可分成三部分&#xff1a; &#xff08;1&#xff09;调用方法&#xff1a;该方法调用异步方法&#xff0c;然后在异步方法执行其任务的…

LeetCode每日打卡 - 汉明距离总和

有点慢&#xff0c;两层循环也可以完成&#xff0c;就是换个方向&#xff0c;外层遍历32大小的bits数组&#xff0c;里层遍历nums的数字每次右移一位&#xff0c;计算方法类似。 class Solution {public int totalHammingDistance(int[] nums) {//int 是4byte &#xff0c; 每…

python编写代码_用 Python 编写干净、可测试、高质量的代码

用 Python 编写干净、可测试、高质量的代码Noah Gift 2010 年 12 月 20 日发布简介 编写软件是人所承担的最复杂的任务之一。AWK 编程语言和 "K and R C" 的作者之一 Brian Kernigan 在 Software Tools 一书中总结了软件开发的真实性质&#xff0c;他说&#xff0c;“…

阿里云服务器被[kthreaddi]挖矿病毒攻击

首先我根本https://blog.csdn.net/weixin_41599103/article/details/115403332这个博客试了下并没有成功&#xff0c;所以应该是被侵入的程序不一样 先去阿里云里看一下详情 明确告诉了是通过docker被攻击了&#xff0c;先将wordpress容器停止并删除容器和镜像 kill掉进行&am…

python canvas画弧度_python画一朵玫瑰给你

听说 python 还能画画&#xff1f;是的&#xff0c;今天亲手画一朵玫瑰给你。turtle 是 python 中一个强大的绘制图像的库&#xff0c;可以用来绘制各种图像&#xff0c;使用起来很方便。但是需要计算各种角度、距离等&#xff0c;所以要想完成一幅复杂的图像还是有一定的难度的…

通俗彻底解读批处理的延迟变量

先说环境变量 一个批处理可以看成是一个程序&#xff0c;它的环境变量就是自己的全局变量。操作系统可以看成是一个大程序&#xff0c;它的环境变量同样也是全局变量&#xff0c;只不过为了和程序的环境变量区别&#xff0c;就叫做全局环境变量了&#xff0c;既然为了区别&…

LeetCode每日打卡 - 4的幂

不使用循环来今天解题 public boolean isPowerOfFour(int n) {return n > 0 && (n & (n-1)) 0 && n % 31;}首先去做了2的幂这道题&#xff0c;并了解了n&(n-1)的过程&#xff0c;然后思考一下这道题:求二进制中1的个数&#xff0c;理解了n&(n-…

【转】探索c#之Async、Await剖析

阅读目录&#xff1a; 基本介绍基本原理剖析内部实现剖析重点注意的地方总结 基本介绍 Async、Await是net4.x新增的异步编程方式&#xff0c;其目的是为了简化异步程序编写&#xff0c;和之前APM方式简单对比如下。 APM方式&#xff0c;BeginGetRequestStream需要传入回调函数…

时间计算题100道_小学数学专项练习:计算题200道,趁早打印给孩子,期末考试拿100分!...

点击上方「升学的秘诀」关注我们&#xff01;获取更多教育经验、方法、学习资料等&#xff0c;每天中午12点与您相约&#xff01;▼说到我们数学学习过程中最重要的是什么&#xff1f;毋庸置疑那就是我们的一个计算能力&#xff0c;计算可以说是贯穿了我们整个数学学习体系&…

幂等性实现 -接口幂等性

接口幂等性 1.什么是幂等性 对于同一笔业务操作&#xff0c;不管调用多少次&#xff0c;得到的结果都是一样的。 也就是方法调用一次和调用多次产生的额外效果是相同的&#xff0c;他就具有幂等性 2.为什么需要幂等性 在系统高并发的环境下&#xff0c;很有可能因为网络&#…

C 怎么读取Cpp文件_python之调用C加速计算(一)

一、前言python语言是目前比较火的语言&#xff0c;很容易上手&#xff0c;对数据处理也比较友好&#xff0c;可以用几行代码就能进行一些简单的数据处理工作。但是对于稍微大型的数值计算&#xff0c;或者一些涉及到大量循环的数值计算python的计算速度有点让人失望。即使是使…

【转】刨根究底字符编码【2.0版】(3):字符编码的由来、演变与ASCII码

为什么需要字符编码 1. 计算机一开始发明出来时是用来解决数字计算问题的&#xff0c;后来人们发现&#xff0c;计算机还可以做更多的事&#xff0c;例如文本处理。 但计算机其实挺“笨”的&#xff0c;它只“认识”010110111000…这样由0和1两个数字组成的二进制数字&#…

JS创建对象的模式介绍

转自http://www.cnblogs.com/asqq/archive/2013/02/01/3194993.html

matplotlib的优点_超详细matplotlib基础介绍!!!

(给Python开发者加星标&#xff0c;提升Python技能)来源&#xff1a;逐梦erhttps://zhumenger.blog.csdn.net/article/details/106530281【导语】&#xff1a;出色的数据可视化&#xff0c;会让你的数据分析等工作锦上添花&#xff0c;让人印(升)象(职)深(加)刻(薪)。matplotli…

【转】WPF PRISM开发入门一( 初始化PRISM WPF程序)

这篇博客将介绍在WPF项目中引入PRISM框架进行开发的一些基础知识。目前最新的PRISM的版本是Prism 6.1.0&#xff0c;可以在Github上获取PRISM的源码。这个系列的博客将选择PRISM 4.1版本来讲解。可以从微软官网上下载到PRISM 4.1相关内容。将下载下来的文件解压开&#xff1a; …

截屏悬浮软件_功能强大,却小巧的录屏软件,不在错过你的王者时刻

看看录屏是一款操作简单。功能强大的录屏软件。他可以设置你录制视频的一个分辨率&#xff0c;帧率以及录制屏幕方向&#xff0c;非常方便&#xff0c;用户将手机摇一摇就可以控制开启和停止录屏&#xff0c;高效录制精彩瞬间&#xff0c;在录制游戏视频的时候也可以做到不掉帧…