前两天刚感受了下泛型接口的in和out,昨天就开始感受神奇的异步方法Async/await,当然顺路也看了眼多线程那几个。其实多线程异步相关的类单个用法和理解都不算困难,但是异步方法Async/await这东西和Task搅到了一起就有点花花肠子。要单说用法其实也好理解,也有不少文章写了。看过上一篇的同学知道,不弄清楚来龙去脉,这世界总感觉不够高清。异步方法究竟怎么个异步法,为什这样设计,有什么意义?昨天想到今天,感觉终于算是讲得通了,一点愚见记下来分享给大家。

  先不着急直奔主题,看看多线程那一家子,再看他们和Async怎么搞基的。

  1 线程和线程池Thread&ThreadPool

  最基本的线程调用工具

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

  执行结果:

  可以看到这Thread和ThreadPool执行不影响主进程执行。Thread和ThreadPool接受的都是委托类型,所以可以单独定义方法在初始化的时候传入,接受的委托都返回void,所以都不能在线程里有返回值。Thead是单开线程,ThreadPool是使用系统的线程池所以性能更好。参数相关注释里有写,其他特性我们不深究,我们知道这两的原理就是使用线程执行无返回值的方法的即可。

  2 并行循环Parallel

  是用多个线程执行循环的工具

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

  说明一下,为了验证并行循环的执行过程,加入lock玩了一下。lock为什么只能lock引用对象是我推测的,如有偏差,概不负责。

  执行结果:

  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

  一个可以有返回值(需要等待)的多线程工具

            //任务
Task.Run(() =>
{
Thread.Sleep();
Console.WriteLine("Task启动执行匿名方法");
});
Console.WriteLine("Task默认不阻塞"); //获取Task.Result会造成阻塞等待task执行
int r = Task.Run(() =>
{
Console.WriteLine("Task启动执行匿名方法并返回值");
Thread.Sleep();
return ;
}).Result;
Console.WriteLine("返回值是{0}", r);

  执行结果:

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

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

------------------------------------------------关于多线程就扯这么多,终于进入主题Async异步方法--------------------------------------------------------------

  异步方法Async&await&Task

  一些点:

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

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

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

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

  5 只有异步方法内使用了 (await关键词描述的)(有返回值的线程Task)才能提现异步方法的优势

  

  写了一个异步方法,一个普通方法进行对比测试。异步方法正确使用的代码如下:(后面几次测试在此基础上稍作修改即可)

        //异步方法
public async Task<int> MethodA(DateTime bgtime, int i)
{
int r = await Task.Run(() =>
{
Console.WriteLine("异步方法{0}Task被执行", i);
Thread.Sleep();
return i * ;
});
Console.WriteLine("异步方法{0}执行完毕,结果{1}", i, r); if (i == )
{
Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
}
return r;
}
//普通方法
public int MethodC(DateTime bgtime, int i)
{
int r = Task.Run(() =>
{
Console.WriteLine("普通多线程方法{0}Task被执行", i);
Thread.Sleep();
return i * ;
}).Result;
Console.WriteLine("普通方法{0}执行完毕,结果{1}", i, r); if (i == )
{
Console.WriteLine("用时{0}", (DateTime.Now - bgtime).TotalMilliseconds);
}
return r;
}

  调用代码:

     public static void ACTest()
{
Asy_ClassA asy = new Asy_ClassA(); DateTime pbgtime = DateTime.Now;
for (int i = ; i < ; i++)
{
asy.MethodC(pbgtime, i);
Console.WriteLine("普通方法{0}调用完成", i);
} DateTime abgtime = DateTime.Now;
for (int i = ; i < ; i++)
{
asy.MethodA(abgtime, i);
Console.WriteLine("异步方法{0}调用完成", i);
}
}

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

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

  

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

  

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

  

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

  

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

  

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

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

  第四次:把ACTesct方法改成Async异步方法,再用await调用asy.MethodA()异步方法。测试结果:

  

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

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

  

  重要的总结:

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

  一些分析:

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

  因为异步方法返回类型是Task<T>,所以获取返回值只能await或者.Result,两者都会让当前方法等待。

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

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

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

  要达到异步方法内等待线程结果不阻塞调用进程,这个方法本身就应该在线程中执行。所以不管返回类型是什么,放到Task中运行后返回的是Task<T>。这样被调用时相当于一个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<T>。也就说内部可以使用await,且方法本身是放到Task中执行的,所以代码返回类型T,方法的返回类型却是Task<T>。

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

----------------------------------------------------------------完--------------------------------------------------------------

  终于算是完了,研究了两天,写了两天。第一天写到很晚,草草结尾,很多同学可能没理解。今天又再次编辑,重新总结分类,排版。应该能讲清楚了吧,这次真的真的写完了。

  觉得有帮助的同学可以推荐或者顶一下。

异步方法的意义何在,Async和await以及Task的爱恨情仇,还有多线程那一家子。的更多相关文章

  1. 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程

    反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑)   背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...

  2. async和await用法(Task)

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

  3. C#的多线程——使用async和await来完成异步编程(Asynchronous Programming with async and await)

    https://msdn.microsoft.com/zh-cn/library/mt674882.aspx 侵删 更新于:2015年6月20日 欲获得最新的Visual Studio 2017 RC ...

  4. C# Thread、ThreadPool、Task、Invoke、BeginInvoke、async、await 汇总

    本文将主要通过"同步调用"."异步调用"."异步回调"三个示例来讲解在用委托执行同一个"加法类"的时候的的区别和利弊. ...

  5. C# 中 async 和 await 的基本使用

    C# 中 async 和 await 的基本使用 前言 经常在 C# 的代码中看到以 Async 结尾的方法,大概知道意为异步方法,但不知道怎么使用,也不知道如何定义. 对于"同步" ...

  6. 【多线程】 Task ,async ,await

    [多线程]Task ,async ,await 一. WinForm 里经常会用到多线程, 多线程的好出就不多说了,来说说多线程比较麻烦的地方 1. UI 线程与其他线程的同步,主要是 Form 和 ...

  7. C#异步编程(async and await)及异步方法同步调用

    1.什么是异步? 异步操作通常用于执行完成时间可能较长的任务,如打开大文件.连接远程计算机或查询数据库=异步操作在主应用程序线程以外的线程中执行.应用程序调用方法异步执行某个操作时,应用程序可在异步方 ...

  8. Async和Await 异步方法

    Async和Await关键字是C#异步编程的核心.通过使用这两个关键字,你可以使用.NET Framework或Windows Runtime的资源创建一个异步方法如同你创建一个同步的方法一样容易.通 ...

  9. 【转】.NET 4.5 使用async和await关键字调用异步方法

    async和await关键字是.NET 4.5新增加的异步编程方式,通过使用这两个关键字可以轻松便捷的编写异步方法.使用async关键字声明异步方法,使用await关键字等待和获取异步方法返回的结果. ...

随机推荐

  1. ★Kali信息收集~ 5.The Harvester:邮箱挖掘器

    官网:http://www.edge-security.com 安装:apt-get install theHarvester 运行:终端输入 theharvester (小写) 用法+参数:(返回邮 ...

  2. 我为NET狂官方面试题

    基础牢不牢测一测便了解,工作没工作测一测便清楚,工作有几年测一测便知道 最近帮人过一遍C#基础,出了点题目,有需要的同志拿走 答案不唯一,官方答案只供参考,若有错误欢迎提出~ 更新ing 1.面向过程 ...

  3. 揭开SQL注入的神秘面纱PPT分享

        SQL注入是一个老生常谈但又经常会出现的问题.该课程是我在公司内部培训的课程,现在分享出来,希望对大家有帮助.     点击这里下载.

  4. 初始Bootstrap

    使用示例 ①下载Bootstrap框架 网址:http://v3.bootcss.com/getting-started/#download ②解压得到三个文件     ③将文件添加进项目后,在页面中 ...

  5. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(23)-权限管理系统-角色组模块

    系列目录 距离上次发布22讲已经有少许日子了,真是太抱歉,最近年关项目比较急,时间太紧,没有时间发布.请大家见谅 接下来我们的目标是 角色组管理 角色组权限设置 用户管理 把角色组授权给用户 给用户分 ...

  6. 匹夫细说C#:从园友留言到动手实现C#虚函数机制

    前言 上一篇文章匹夫通过CIL代码简析了一下C#函数调用的话题.虽然点击进来的童鞋并不如匹夫预料的那么多,但也还是有一些挺有质量的来自园友的回复.这不,就有一个园友提出了这样一个代码,这段代码如果被编 ...

  7. Scala集合和Java集合对应转换关系

    作者:Syn良子 出处:http://www.cnblogs.com/cssdongl 转载请注明出处 用Scala编码的时候,经常会遇到scala集合和Java集合互相转换的case,特意mark一 ...

  8. ASP.NET Core 中文文档 第四章 MVC(3.2)Razor 语法参考

    原文:Razor Syntax Reference 作者:Taylor Mullen.Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:何镇汐 什么是 Razor? Razor 是一 ...

  9. LINQ to SQL语句(17)之对象加载

    对象加载 延迟加载 在查询某对象时,实际上你只查询该对象.不会同时自动获取这个对象.这就是延迟加载. 例如,您可能需要查看客户数据和订单数据.你最初不一定需要检索与每个客户有关的所有订单数据.其优点是 ...

  10. VS2015突然报错————Encountered an unexpected error when attempting to resolve tag helper directive '@addTagHelper' with value 'Microsoft.AspNet.Mvc.Razor.TagHelpers.UrlResolutionTagHelper

    Encountered an unexpected error when attempting to resolve tag helper directive '@addTagHelper' with ...