一文说通C#中的异步编程

天天写,不一定就明白。

又及,前两天看了一个关于同步方法中调用异步方法的文章,里面有些概念不太正确,所以整理了这个文章。

一、同步和异步。

先说同步。

同步概念大家都很熟悉。在异步概念出来之前,我们的代码都是按同步的方式写的。简单来说,就是程序严格按照代码的逻辑次序,一行一行执行。

看一段代码:

public static void Main(string[] args){    Console.WriteLine("Syc proccess - start");

    Console.WriteLine("Syc proccess - enter Func1");    func1();    Console.WriteLine("Syc proccess - out Func1");

    Console.WriteLine("Syc proccess - enter Func2");    func2();    Console.WriteLine("Syc proccess - out Func2");

    Console.WriteLine("Syc proccess - enter Func3");    func3();    Console.WriteLine("Syc proccess - out Func3");

    Console.WriteLine("Syc proccess - done");}

private static void func1(){    Console.WriteLine("Func1 proccess - start");    Thread.Sleep(1000);    Console.WriteLine("Func1 proccess - end");}

private static void func2(){    Console.WriteLine("Func2 proccess - start");    Thread.Sleep(3000);    Console.WriteLine("Func2 proccess - end");}

private static void func3(){    Console.WriteLine("Func3 proccess - start");    Thread.Sleep(5000);    Console.WriteLine("Func3 proccess - end");}

这是一段简单的通常意义上的代码,程序按代码的次序同步执行,看结果:

Syc proccess - startSyc proccess - enter Func1Func1 proccess - startFunc1 proccess - endSyc proccess - out Func1Syc proccess - enter Func2Func2 proccess - startFunc2 proccess - endSyc proccess - out Func2Syc proccess - enter Func3Func3 proccess - startFunc3 proccess - endSyc proccess - out Func3Syc proccess - done

没有任何意外。

    为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13357981.html

那异步呢?

异步,来自于对同步处理的改良和优化。

应用中,经常会有对于文件或网络、数据库的IO操作。这些操作因为IO软硬件的原因,需要消耗很多时间,但通常情况下CPU计算量并不大。在同步的代码中,这个过程会被阻塞。直白的说法就是这一行代码没执行完成,程序就得等着,等完成后再执行下一行代码,而这个等待的时间中,CPU资源就被浪费了,闲着了,什么也没做。(当然,操作系统会调度CPU干别的,这儿不抬杠。)

异步编程模型和规范因此出现了,通过某种机制,让程序在等着IO的过程中,继续做点别的事,等IO的过程完成了,再回来处理IO的内容。这样CPU也没闲着,在等IO的过程中多做了点事。反映到用户端,就感觉程序更快了,用时更短了。

下面重点说一下异步编程相关的内容。

二、异步编程

C#中,异步编程,一个核心,两个关键字。

一个核心是指TaskTask<T>对象,而两个关键字,就是asyncawait

从各种渠道给出的异步编程,都是下面的方式:

async Task function(){  /* your code here */}

然后调用的方式:

await function();

是这样的吗?嗯,图样图森破~~~

我们来看代码:

static async Task Main(string[] args){    Console.WriteLine("Async proccess - start");

    Console.WriteLine("Async proccess - enter Func1");    await func1();    Console.WriteLine("Async proccess - out Func1");

    Console.WriteLine("Async proccess - enter Func2");    await func2();    Console.WriteLine("Async proccess - out Func2");

    Console.WriteLine("Async proccess - enter Func3");    await func3();    Console.WriteLine("Async proccess - out Func3");

    Console.WriteLine("Async proccess - done");

    Console.WriteLine("Main proccess - done");}

private static async Task func1(){    Console.WriteLine("Func1 proccess - start");    Thread.Sleep(1000);    Console.WriteLine("Func1 proccess - end");}

private static async Task func2(){    Console.WriteLine("Func2 proccess - start");    Thread.Sleep(3000);    Console.WriteLine("Func2 proccess - end");}

private static async Task func3(){    Console.WriteLine("Func3 proccess - start");    Thread.Sleep(5000);    Console.WriteLine("Func3 proccess - end");}

跑一下结果:

Async proccess - startAsync proccess - enter Func1Func1 proccess - startFunc1 proccess - endAsync proccess - out Func1Async proccess - enter Func2Func2 proccess - startFunc2 proccess - endAsync proccess - out Func2Async proccess - enter Func3Func3 proccess - startFunc3 proccess - endAsync proccess - out Func3Async proccess - doneMain proccess - done

咦?这个好像跟同步代码的执行结果没什么区别啊?

嗯,完全正确。上面这个代码,真的是同步执行的。

这是异步编程的第一个容易错误的理解:asyncawait的配对。

三、async和await的配对

在异步编程的规范中,async修饰的方法,仅仅表示这个方法在内部有可能采用异步的方式执行,CPU在执行这个方法时,会放到一个新的线程中执行。

那这个方法,最终是否采用异步执行,不决定于是否用await方式调用这个方法,而决定于这个方法内部,是否有await方式的调用。

看代码,很容易理解:

private static async Task func1(){    Console.WriteLine("Func1 proccess - start");    Thread.Sleep(1000);    Console.WriteLine("Func1 proccess - end");}

这个方法,因为方法内部没有await调用,所以这个方法永远会以同步方式执行,不管你调用这个方法时,有没有await

而下面这个代码:

private static async Task func1(){    Console.WriteLine("Func1 proccess - start");    await Task.Run(() => Thread.Sleep(1000));    Console.WriteLine("Func1 proccess - end");}

因为这个方法里有await调用,所以这个方法不管你以什么方式调用,有没有await,都是异步执行的。

看代码:

static async Task Main(string[] args){    Console.WriteLine("Async proccess - start");

    Console.WriteLine("Async proccess - enter Func1");    func1();    Console.WriteLine("Async proccess - out Func1");

    Console.WriteLine("Async proccess - enter Func2");    func2();    Console.WriteLine("Async proccess - out Func2");

    Console.WriteLine("Async proccess - enter Func3");    func3();    Console.WriteLine("Async proccess - out Func3");

    Console.WriteLine("Async proccess - done");

    Console.WriteLine("Main proccess - done");

    Console.ReadKey();}

private static async Task func1(){    Console.WriteLine("Func1 proccess - start");    await Task.Run(() => Thread.Sleep(1000));    Console.WriteLine("Func1 proccess - end");}

private static async Task func2(){    Console.WriteLine("Func2 proccess - start");    await Task.Run(() => Thread.Sleep(3000));    Console.WriteLine("Func2 proccess - end");}

private static async Task func3(){    Console.WriteLine("Func3 proccess - start");    await Task.Run(() => Thread.Sleep(5000));    Console.WriteLine("Func3 proccess - end");}

输出结果:

Async proccess - startAsync proccess - enter Func1Func1 proccess - startAsync proccess - out Func1Async proccess - enter Func2Func2 proccess - startAsync proccess - out Func2Async proccess - enter Func3Func3 proccess - startAsync proccess - out Func3Async proccess - doneMain proccess - doneFunc1 proccess - endFunc2 proccess - endFunc3 proccess - end

结果中,在长时间运行Thread.Sleep的时候,跳出去往下执行了,是异步。

又有问题来了:不是说异步调用要用await吗?

我们把await加到调用方法的前边,试一下:

static async Task Main(string[] args){    Console.WriteLine("Async proccess - start");

    Console.WriteLine("Async proccess - enter Func1");    await func1();    Console.WriteLine("Async proccess - out Func1");

    Console.WriteLine("Async proccess - enter Func2");    await func2();    Console.WriteLine("Async proccess - out Func2");

    Console.WriteLine("Async proccess - enter Func3");    await func3();    Console.WriteLine("Async proccess - out Func3");

    Console.WriteLine("Async proccess - done");

    Console.WriteLine("Main proccess - done");

    Console.ReadKey();}

跑一下结果:

Async proccess - startAsync proccess - enter Func1Func1 proccess - startFunc1 proccess - endAsync proccess - out Func1Async proccess - enter Func2Func2 proccess - startFunc2 proccess - endAsync proccess - out Func2Async proccess - enter Func3Func3 proccess - startFunc3 proccess - endAsync proccess - out Func3Async proccess - doneMain proccess - done

嗯?怎么又像是同步了?

对,这是第二个容易错误的理解:await是什么意思?

四、await是什么意思

提到await,就得先说说Wait

字面意思,Wait就是等待。

前边说了,异步有一个核心,是Task。而Task有一个方法,就是Wait,写法是Task.Wait()。所以,很多人把这个Waitawait混为一谈,这是错的

这个问题来自于Task。C#里,Task不是专为异步准备的,它表达的是一个线程,是工作在线程池里的一个线程。异步是线程的一种应用,多线程也是线程的一种应用。Wait,以及StatusIsCanceledIsCompletedIsFaulted等等,是给多线程准备的方法,跟异步没有半毛钱关系。当然你非要在异步中使用多线程的Wait或其它,从代码编译层面不会出错,但程序会。

尤其,Task.Wait()是一个同步方法,用于多线程中阻塞等待。

在那个「同步方法中调用异步方法」的文章中,用Task.Wait()来实现同步方法中调用异步方法,这个用法本身就是错误的。 异步不是多线程,而且在多线程中,多个Task.Wait()使用也会死锁,也有解决和避免死锁的一整套方式。

再说一遍:Task.Wait()是一个同步方法,用于多线程中阻塞等待,不是实现同步方法中调用异步方法的实现方式。

说回await。字面意思,也好像是等待。是真的吗?

并不是,await不完全是等待的意思。

在异步中,await表达的意思是:当前线程/方法中,await引导的方法出结果前,跳出当前线程/方法,从调用当前线程/方法的位置,去执行其它可能执行的线程/方法,并在引导的方法出结果后,把运行点拉回到当前位置继续执行;直到遇到下一个await,或线程/方法完成返回,跳回去刚才外部最后执行的位置继续执行。

有点绕,还是看代码:

  static async Task Main(string[] args)  {1     Console.WriteLine("Async proccess - start");

2     Console.WriteLine("Async proccess - enter Func1");3     func1();4     Console.WriteLine("Async proccess - out Func1");

5     Console.WriteLine("Async proccess - done");

6         Thread.Sleep(2000);

7     Console.WriteLine("Main proccess - done");

8    Console.ReadKey();  }

  private static async Task func1()  {9     Console.WriteLine("Func1 proccess - start");10    await Task.Run(() => Thread.Sleep(1000));11    Console.WriteLine("Func1 proccess - end");  }

这个代码,执行时是这样的:顺序执行1、2、3,进到func1,执行9、10,到10时,有await,所以跳出,执行4、5、6。而6是一个长时等待,在等待的过程中,func1的10运行完成,运行点跳回10,执行11并结束方法,再回到6等待,结束等待后继续执行7、8结束。

我们看一下结果:

Async proccess - startAsync proccess - enter Func1Func1 proccess - startAsync proccess - out Func1Async proccess - doneFunc1 proccess - endMain proccess - done

映证了这样的次序。

在这个例子中,await在控制异步的执行次序。那为什么要用等待这么个词呢?是因为await确实有等待结果的含义。

这是await的第二层意思。

五、await的第二层意思:等待拿到结果

await确实有等待的含义。等什么?等异步的运行结果。

看代码:

static async Task Main(string[] args){    Console.WriteLine("Async proccess - start");

    Console.WriteLine("Async proccess - enter Func1");    Task<int> f = func1();    Console.WriteLine("Async proccess - out Func1");

    Console.WriteLine("Async proccess - done");

    int result = await f;

    Console.WriteLine("Main proccess - done");

    Console.ReadKey();}

private static async Task<int> func1(){    Console.WriteLine("Func1 proccess - start");    await Task.Run(() => Thread.Sleep(1000));    Console.WriteLine("Func1 proccess - end");

    return 5;}

比较一下这段代码和上一节的代码,很容易搞清楚执行过程。

这个代码,完成了这样一个需求:我们需要使用func1方法的返回值。我们可以提前去执行这个方法,而不急于拿到方法的返回值,直到我们需要使用时,再用await去获取到这个返回值去使用。

这才是异步对于我们真正的用处。对于一些耗时的IO或类似的操作,我们可以提前调用,让程序可以利用执行过程中的空闲时间来完成这个操作。等到我们需要这个操作的结果用于后续的执行时,我们await这个结果。这时候,如果await的方法已经执行完成,那我们可以马上得到结果;如果没有完成,则程序将继续执行这个方法直到得到结果。

六、同步方法中调用异步

正确的方法只有一个:

func1().GetAwaiter().GetResult();

这其实就是await的一个变形。

(全文完)

(0)

相关推荐

  • 结合 AOP 轻松处理事件发布处理日志

    结合 AOP 轻松处理事件发布处理日志 Intro 前段时间,实现了 EventBus 以及 EventQueue 基于 Event 的事件处理,但是没有做日志(EventLog)相关的部分,原本想增 ...

  • 多线程之旅(Thread)

    在上篇文章中我们已经知道了多线程是什么了,那么它到底可以干嘛呢?这里特别声明一个前面的委托没看的同学可以到上上上篇博文查看,因为多线程要经常使用到委托.源码一.异步.同步1.同步(在计算的理解总是要你 ...

  • 小心 HttpClient 中 FormUrlEncodeContent 的 bug

    小心 HttpClient 中 FormUrlEncodeContent 的 bug Intro 最近发现活动室预约项目里的上传图片有时候会有问题,周末找时间测试了一下,发现小图片的上传没问题,大图片 ...

  • C# 异步编程

    基于Task的异步编程模式(TAP)是Microsoft为.Net平台下使用Task进行编程所提供的一组建议,这种模式提供了可以被await消耗(调用)方法的APIs,并且当使用async关键字编写遵 ...

  • C#线程学习笔记八:async & await入门一

    一.涉及内容 async & await是C# 5.0引入的,控制台输出所使用的$符号(拼接字符串)是C# 6.0引入的,其功能类似于string.Format()方法. 二.多线程.异步.同 ...

  • 【5min+】帮我排个队,谢谢。await Task.Yield()

    系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...

  • 一文说通C#中的异步迭代器

    今天来写写C#中的异步迭代器 - 机制.概念和一些好用的特性 迭代器的概念 迭代器的概念在C#中出现的比较早,很多人可能已经比较熟悉了. 通常迭代器会用在一些特定的场景中. 举个例子:有一个forea ...

  • 字字组词,字字的部首,字字的笔画,字字的读音,字字的偏旁部首,康熙字典字的解释,说文解字中字的解释,...

    [dictionary] 一种工具书,搜集单字,按某种检字法(如音序检字法.部首笔画检字法.四角号码检字法.上下形检字法)排列,并一一注明音.义.用法,有的还详注引证书籍,同义字与反义字等,而有的只注 ...

  • 【深圳诗歌】No.81330期A版||​对CBA2020/2021男篮赛季感怀 文/谢瑛中

    欢迎阅读 深 圳 诗 歌 <深圳诗歌>平台旨在打造华语文学的精神新高地!我们从杂录的角度编发作家老师朋友们的作品,诗歌散文小说等全部文体我们都可以收录.仿古体我们也收录,读者朋友们可以领略 ...

  • ​我们能否和抑郁焦虑彻底说再见?一文盘点心境障碍中的神经可塑性密匙miRNA

    作者:刘芳 编审:王新凯 排版:王落尘 2019 年世卫组织数据显示,全球有超过 3.5 亿抑郁症患者,近十年来增速约 18%.同年,我国泛抑郁人数超过 9500 万人,每年造成经济损失 78 亿美元 ...

  • 文涛 | 生活中的一切,都只在于你怎么定义它

    每日推送喜乐文涛老师课程精华.传递正能量 第一时间获知现场课.沙龙信息 所谓的苦难不是坏事儿,所谓经历的折磨,痛苦,其实看你怎么定义它,看你怎么说它.你觉得苦,那就真的很苦. 经常在网上看到说,成年人 ...

  • 为什么“非遗 旅游”在文旅融合中备受青睐?

    随着我国社会经济的持续稳定发展,消费升级和需求衍化已成为新时代社会消费领域的显性问题.在文旅行业,我们也能清晰地看到,传统的观光旅游已经难以满足旅游者日益多元化.个性化的消费需求,文旅消费供给侧改革亟 ...

  • 我工作中不可或缺的编程软件工具!

    昨天 以下文章来源于程序员鱼皮 ,作者鱼皮 程序员鱼皮鹅厂全栈开发,持续分享编程技法和实用项目 大家好,今天分享下我在日常工作中最最最常用的宝贝软件和工具. 开发 JetBrains 全家桶 JetB ...

  • 一文读懂中唐诗人关系网(合篇)什么样的生命才精彩?

    韩柳文章刘诗豪,郊囚贾奴贺鬼小. 新乐旗下元白绅,卢纶应物志和高. 长卿张继崔护孤,薛涛诗笺颜色好. 1.幼年苦逼大赛第一名 苏轼曾评价一人:文起八代之衰,道济天下之溺,忠犯人主之怒,勇夺三军之帅. ...

  • 印度又挑事,一文看懂中、印两国化工品进出口贸易格局

    平头哥了解到,由于中印边界冲突,造成印度国内对中国制造的产品产生抵触行动,誓言要抵制中国制造,让中国经济停止增长.印度国防部长说,印度已经不是1962年的印度,言下之意,印度军力已经很强大,不会惧怕与 ...