1、线程和线程池Thread&ThreadPool

  1. //线程初始化时执行方法可以带一个object参数,为了传入自定义参数,所以执行需单独调用用于传参。
  2. Console.WriteLine("执行线程");
  3. Thread th = new Thread((objParam) =>
  4. {
  5. Console.WriteLine("线程启动,执行匿名方法,有无参数{0}", objParam != null);
  6. });
  7. th.IsBackground = true;
  8. object objP = new object();
  9. th.Start(objP);
  10. //线程池
  11. //线程池初始化执行方法必须带一个object参数,接受到的值是系统默认NULL(不明),所以初始化完成自动调用
  12. Console.WriteLine("执行线程池");
  13. ThreadPool.QueueUserWorkItem((objparam) =>
  14. {
  15. Console.WriteLine("线程池加入的匿名方法被执行。");
  16. });

执行结果:

2、并行循环Parallel

  1. int result = 0;
  2. int lockResult = 0;
  3. object lb = new object();
  4. //并行循环
  5. //并行应该用于一次执行多个相同任务,或计算结果和循环的游标没有关系只和执行次数有关系的计算
  6. Console.WriteLine("执行并行循环");
  7. Parallel.For(0, 10, (i) =>
  8. {
  9. result = result + 2;
  10. //lock只能lock引用类型,利用引用对象的地址唯一作为锁,实现lock中的代码一次只能一个线程访问
  11. //lock让lock里的代码在并行时变为串行,尽量不要在parallel中用lock(lock内的操作耗时小,lock外操作耗时大时,并行还是起作用)
  12. lock(lb)
  13. {
  14. lockResult = lockResult + 2;
  15. Thread.Sleep(100);
  16. Console.WriteLine("i={0},lockResult={1}", i, lockResult);
  17. }
  18. Console.WriteLine("i={0},result={1}", i, result);
  19. });

执行结果:

Parallel用法很简单,就是Parallel.For(游标开始值, 游标结束, int参数的Action),传入action的方法接受的int参数就是当前执行的游标。

跑题开始-------------------------------(手贱要在并行里写lock还要sleep刚好形成规律,以下是写博时发现的,没兴趣的同学可以跳过)

通过结果我们可以看出,首先执行顺序是随机的,可以猜到一次是把游标的取值分别当参数传给多个线程执行action即可。后面的结果也验证了这一点,lockResult不用说,不管多少线程到这都得排队执行,所以结果递增。再看result,上来就变成了10,可以推出遇到lock之前已经被加了5次,那么应该是一次4个线程喽(大家肯定觉得应该是5个,开始我也是这样觉得,往下看)。

再看result其实也不是没规律,可以看出从10到20也是递增,但到了20就不增加了(因缺思亭)。我们模拟下(按5个线程模拟不符合结果,我就直接按合理的情况推一遍)。

1、首先可以4个线程ABCD同时执行,都到了lock这停住,那这时result被加了4次是8。

2、然后一个线程A执行lock里的代码,其他的BCD等待(不是sleep仍然占用cpu),执行完输出lockResult=2(第一行)。

这时继续往下应该输出result=8对吧,为什么是result=10。注意lock里有一个Thread.Sleep(100),这就是关键。在lock里sleep会怎样,当前线程A释放cpu 100ms,这时就可以再来一个线程E执行到lock这也停住了,result是不是就是10了。

3、这个线程A醒来优先级最高挤掉一个线程往下继续输出result=10(第二行)。这时刚才被挤掉的线程又恢复占用cpu状态,就是BCDE四个线程。

4、同理,BCDE四个等待线程的又有一个进入lock然后又sleep,又可以有一个线程来把result加2,这时循环这个过程,result也呈现出规律。

5、为什么result后面几次都是20,因为总共执行10次,首先四个线程执行了4次,然后一个新线程执行第5次后,第1次执行的线程才输出第5次执行后的结果,第2次输出第6次。。。第6次输出第10次(第6行就是result=20),后面四次已经执行过result加2,所以只输出结果20。

如果把Thread.Sleep(100)去掉result就不再有这么明显的规律。因为sleep让cpu可以释放与lock等待共同作用让线程执行形成一个先后顺序的队列。sleep放到lock外也不行,sleep会释放cpu,放到lock外,没有lock占用cpu,lock前就不一定执行了几次。

为什么一次是四个线程呢,很容易想到,我CPU四核的。。。就这么简单。。

跑题结束---------------------------------

  

通过以上分析,并行是个什么东西大家应该有所了解了,继续。

3、任务Task

  1. //任务
  2. Task.Run(() =>
  3. {
  4. Thread.Sleep(200);
  5. Console.WriteLine("Task启动执行匿名方法");
  6. });
  7. Console.WriteLine("Task默认不阻塞");
  8. //获取Task.Result会造成阻塞等待task执行
  9. int r = Task.Run(() =>
  10. {
  11. Console.WriteLine("Task启动执行匿名方法并返回值");
  12. Thread.Sleep(1000);
  13. return 5;
  14. }).Result;
  15. Console.WriteLine("返回值是{0}", r);

执行结果:

用法如上,好像使用的是线程池。传入方法不能有参数,可以有返回值。要获得结果,要在Run()(返回Task类型)之后调用Task类型的Result属性获取。可以看出,获取结果时,Task是会阻塞当前进程的,等待线程执行完毕才继续。

Task好用,关键点就是有返回值,可以获取结果。

4、异步方法Async&await&Task

重点:

1、异步方法需要Async关键字修饰

2、异步方法的返回类型只能是void或Task

3、返回值类型是T时,异步方法返回类型必须是Task

4、await可以用于async方法和 async方法中的task(通过3、4两点大家应该能猜到,异步方法本身其实就是一个Task或者说和自己内部的Task在同一线程)

5、只有异步方法内使用了(await关键词描述的)(有返回值的线程Task)才能提现异步方法的优势写了一个异步方法,一个普通方法进行对比测试。异步方法正确使用的代码如下:(后面几次测试在此基础上稍作修改即可)

  1. //异步方法
  2. public async Task<int> MethodA(DateTime bgtime, int i)
  3. {
  4. int r = await Task.Run(() =>
  5. {
  6. Console.WriteLine("异步方法{0}Task被执行", i);
  7. Thread.Sleep(100);
  8. return i * 2;
  9. });
  10. Console.WriteLine("异步方法{0}执行完毕,结果{1}", i, r);
  11. if (i == 49)
  12. {
  13. Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
  14. }
  15. return r;
  16. }
  17. //普通方法
  18. public int MethodC(DateTime bgtime, int i)
  19. {
  20. int r = Task.Run(() =>
  21. {
  22. Console.WriteLine("普通多线程方法{0}Task被执行", i);
  23. Thread.Sleep(100);
  24. return i * 2;
  25. }).Result;
  26. Console.WriteLine("普通方法{0}执行完毕,结果{1}", i, r);
  27. if (i == 49)
  28. {
  29. Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
  30. }
  31. return r;
  32. }
  1. //异步方法
  2. public async Task<int> MethodA(DateTime bgtime, int i)
  3. {
  4. int r = await Task.Run(() =>
  5. {
  6. Console.WriteLine("异步方法{0}Task被执行", i);
  7. Thread.Sleep(100);
  8. return i * 2;
  9. });
  10. Console.WriteLine("异步方法{0}执行完毕,结果{1}", i, r);
  11. if (i == 49)
  12. {
  13. Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
  14. }
  15. return r;
  16. }
  17. //普通方法
  18. public int MethodC(DateTime bgtime, int i)
  19. {
  20. int r = Task.Run(() =>
  21. {
  22. Console.WriteLine("普通多线程方法{0}Task被执行", i);
  23. Thread.Sleep(100);
  24. return i * 2;
  25. }).Result;
  26. Console.WriteLine("普通方法{0}执行完毕,结果{1}", i, r);
  27. if (i == 49)
  28. {
  29. Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
  30. }
  31. return r;
  32. }

测试开始!------------------------------------------------------------------------------------------------

  • 第一次:都获取Task的返回结果,异步方法使用await获取,普通方法使用Task.Run().Result获取。

测试结果:

    可以发现普通方法由于阻塞执行都是按顺序执行,多线程失去意义。异步方法则并行执行,重要的是计算结果一样。所以在方法内需要使用Task结果时,异步方法使用await不阻塞调用进程优势明显。

  • 第二次:异步方法中不使用await,使用和普通方法一样的Task.Run().Result获取结果。测试结果:

    可以看到用时和执行顺序都一样。所以没有await的情况下,异步方法等待Task结果时一样会阻塞调用进程。

  • 第三次:都只调用Task执行,不获取结果。测试结果:

    可以看到,不管是普通方法还是异步方法都是多个线程并行执行,所以不获取结果的时,异步方法和普通多线程方法性能一样。

    在这次测试基础上,让异步方法await一个不返回结果的Task会发现,异步方法内还是会等待Task执行完毕。所以只要使用await不管是方法还是Task,有无返回结果,后面的代码都要等待其执行完毕。

如下面示例:

  1. public async void AsyncMethod()
  2. {
  3. Console.WriteLine("开始异步代码");
  4. Thread.Sleep(5000);
  5. Console.WriteLine("开始执行异步方法");
  6. //只要有await关键字,当前线程默认都会阻塞当前线程,但不阻塞主线程,如想不阻塞当前线程,当前线程不应包含await执行
  7. int R = await Task.Run(() =>
  8. {
  9. for (int i = 0; i < 5; i++)
  10. {
  11. Console.WriteLine("异步执行" + i.ToString() + "..");
  12. Thread.Sleep(2000); ; //模拟耗时操作
  13. }
  14. return 1;
  15. });
  16. //直接Task.Run有返回值时会阻塞当前线程,同时也会阻塞主线程
  17. //int R = Task.Run(() =>
  18. //{
  19. // for (int i = 0; i < 5; i++)
  20. // {
  21. // Console.WriteLine("异步执行" + i.ToString() + "..");
  22. // Thread.Sleep(2000); ; //模拟耗时操作
  23. // }
  24. // return 1;
  25. //}).Result;
  26. //不会阻塞当前线程,也不会阻塞主线程
  27. //Task.Run(() =>
  28. //{
  29. // for (int i = 0; i < 5; i++)
  30. // {
  31. // Console.WriteLine("异步执行" + i.ToString() + "..");
  32. // Thread.Sleep(2000); ; //模拟耗时操作
  33. // }
  34. //});
  35. Console.WriteLine("异步代码执行完毕");
  36. }
  • 第四次:把ACTesct方法改成Async异步方法,再用await调用asy.MethodA()异步方法。测试结果:

    await只能在异步方法中使用(为什么这样设计后面分析),所以ACTest需要改成Async。可以看到,异步方法调用时被await了一样会等待。所以异步方法应该没有返回值或者调用时不关注返回结果才有效。

测试完毕!-----------------------------------------------------------------------------------

5、总结:

  1.   【意义】异步方法的意义就是保证一个进程使用多线程多次执行一个方法时,不会因为其中某一次执行阻塞调用进程
  2.   
  3.   【原理】利用方法内Task调用新线程,await使方法内等待Task结果时调用进程不被阻塞,多次调用相当于多个线程并行。(不被阻塞的原因应该是异步方法本身就和内部的Task跑在一个线程里)
  4.   
  5.   【区别】普通方法只用Task也可以并行,当方法内需要Task返回值时,等待Task结果就会阻塞调用进程
  6.   
  7.   【应用】主要应用在没有返回值,使用线程且需要线程返回结果的方法

一些分析:

  • 1.异步方法有返回值会怎样?

    因为异步方法返回类型是Task,所以获取返回值只能await或者.Result,两

者都会让当前方法等待。

  • 2.那么异步方法是不是没有作用了?

    如果是用.Result获取,那么是。如果是await就不一定了。await只能在async方法中使用,所以await获取异步方法返回值的方法也是异步的,再往上最终只能肯定是一个普通方法调用异步方法。是否有用取决于普通方法内调用最上层异步方法的方式。

  • 3.为什么返回值类型是T,方法返回类型需要是Task?

    要达到异步方法内等待线程结果不阻塞调用进程,这个方法本身就应该在线程中执行。所以不管返回类型是什么,放到Task中运行后返回的是Task。这样被调用时相当于一个Task.Run(),也就可以实现异步方法await了。

  • 4.为什么要实现异步方法await可等待?

    异步方法的await其实第二点已经分析了,实现异步方法await可以允许异步方法内继续调用异步方法,把异步操作从底层向上层传递。而能够传递到的最上层是什么,是static void Main(),所以最终还是普通方法调用异步方法。也就是说不能继续使用await等待异步方法的结果了,当最上层不关注返回结果时,不管内部有多少次await异步方法的调用,依然还是多线程的并行。如果最上层非要关注异步方法的返回结果,用.Result获取其结果,那我无话可说。

  • 5.关于Async和await。

    await其实不光是一个简单的让下一行代码等待异步方法或Task结果的关键字。应该理解成一个扩大当前Task代码执行范围的命令。

    从最开始的await Task让整个异步方法B都能在Task中运行(所以普通方法调用异步方法B时,B内await Task结果就不会阻塞调用进程)。

    到异步方法A中await异步方法B让异步方法A和B都在同一Task内运行(所以普通方法调用异步方法A时,A内await异步方法B的结果和B内await Task的结果就不会阻塞调用进程)

    Async用于标识一个方法是异步方法,约束其返回类型为Task。也就说内部可以使用await,且方法本身是放到Task中执行的,所以代码返回类型T,方法的返回类型却是Task。

    最后一定要区别异步方法和普通多线程方法的用处,他们的关键区别就是是否需要单独等待线程的执行结果。不要把异步方法当多线程方法用了。

原文地址:https://www.cnblogs.com/xianyudotnet/p/5716908.html

Thread&ThreadPool、Parallel、Async和Await用法总结的更多相关文章

  1. .NET多线程(Thread,ThreadPool,Task,Async与Await)

    .NET多线程是什么? 进程与线程 进程是一种正在执行的程序. 线程是程序中的一个执行流. 多线程是指一个程序中可以同时运行多个不同的线程来执行不同的任务. .NET中的线程 Thread是创建和控制 ...

  2. async和await用法

    原文:async和await用法 要理解async和await的用法,首先要了解Task相关知识,这里不做说明,因为这不是本文的重点. 如果你已经对Task很了解,那么如何使用async和await, ...

  3. async和await用法(Task)

    原文:async和await用法 要理解async和await的用法,首先要了解Task相关知识,这里不做说明,因为这不是本文的重点. 如果你已经对Task很了解,那么如何使用async和await, ...

  4. 浅谈async函数await用法

    今天状态不太好,睡久了懵一天. 以前只是了解过async函数,并还没有很熟练的运用过,所以先开个坑吧,以后再结合实际来更新下,可能说的有些问题希望大家指出. async和await相信大家应该不陌生, ...

  5. C#中async和await用法

    .net 4.5中新增了async和await这一对用于异步编程的关键字. async放在方法中存在await代码的方法中,await放在调用返回Task的方法前. class Class1 { pr ...

  6. 关于Thread ThreadPool Parallel 的一些小测试demo

    using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading ...

  7. C# Async与Await用法

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  8. c# async和await 用法(阻塞与不阻塞)

    void PagePaint() { Console.WriteLine("Paint Start"); Paint(); Console.WriteLine("Pain ...

  9. 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读

    记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...

随机推荐

  1. .NET使用本地outlook客户端发送邮件

    1.添加Microsoft.Office.Interop.Outlook引用 2.封装发送邮件方法 using System; using System.Configuration; using Sy ...

  2. Spring Boot 使用 Log4j2 & Logback 输出日志到 EKL

    文章目录 1.ELK 介绍 2.环境.软件准备 3.ELK 环境搭建 4.Spring Boot 配置示例 4.1.Log4j2 方式配置 4.2.Logback 方式配置 1.ELK 介绍 ELK ...

  3. Python【day 11】闭包

    闭包 1.闭包的概念: 嵌套函数中,父级函数的变量,在子集函数中用到了(访问.修改.返回),那么这个变量就被保护起来了 只有自己可以修改,父级函数()()就是闭包函数 2.闭包的特点: 1.常驻内存 ...

  4. Lucene BooleanQuery相关算法

    BooleanQuery对两种不同查询场景执行不同的算法: 场景1: 所有的子句都必须满足,而且所有的子句里没有嵌套BooleanQuery. 例: a AND b AND c 上面语句表示要同时包含 ...

  5. 百度地图分布图(百度地图api司机位置实时定位分布图)

    就类似于我们使用共享单车app的时候,可以看到我们周围的空闲单车分布.e代驾在后台管理系统需求里也有此功能,目的是为了实时看到目标城市下的所有司机状态. 一.controller //controll ...

  6. Docker(一) - CentOS7中安装Docker - (视频教程)

    Docker的使用越来越多,安装也相对简单.本文使用视频的方式展示在CentOS7系统中安装Docker,本文更适合于准备入门学习Docker的童靴. 以下视频,请带上耳机开始聆听 (双击全屏播放) ...

  7. Android培训准备资料之UI一些相似控件和控件一些相似属性之间的区别

    这一篇博客主要收集五大布局中的一些相似控件和控件一些相似属性之间的区别 ImageView ImageButton Button 三者有啥区别? (1)Button继承自TextView,ImageV ...

  8. 【原创】CentOS 7 安装airflow

    该文是基于python虚拟化环境来安装,非虚拟化也是一样,虚拟化我只是不想破环系统环境. 安装python虚拟环境 pip install virtualenv 设置环境变量 sudo vi /etc ...

  9. composer install 出现“Please provide a valid cache path”

    本文背景:通过deployer部署PHP项目[deployer部署工具:https://deployer.org/] 问题:Php 的laravel框架中执行 composer install 后, ...

  10. tornado 文件上传

    服务端 def post(self, *args, **kwargs): # content_type # filename # body file_data=self.request.files i ...