一文说通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 - start
Syc proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Syc proccess - out Func1
Syc proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Syc proccess - out Func2
Syc proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Syc proccess - out Func3
Syc proccess - done
没有任何意外。
为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13357981.html
那异步呢?
异步,来自于对同步处理的改良和优化。
应用中,经常会有对于文件或网络、数据库的IO操作。这些操作因为IO软硬件的原因,需要消耗很多时间,但通常情况下CPU计算量并不大。在同步的代码中,这个过程会被阻塞。直白的说法就是这一行代码没执行完成,程序就得等着,等完成后再执行下一行代码,而这个等待的时间中,CPU资源就被浪费了,闲着了,什么也没做。(当然,操作系统会调度CPU干别的,这儿不抬杠。)
异步编程模型和规范因此出现了,通过某种机制,让程序在等着IO的过程中,继续做点别的事,等IO的过程完成了,再回来处理IO的内容。这样CPU也没闲着,在等IO的过程中多做了点事。反映到用户端,就感觉程序更快了,用时更短了。
下面重点说一下异步编程相关的内容。
二、异步编程
C#中,异步编程,一个核心,两个关键字。
一个核心是指Task和Task<T>对象,而两个关键字,就是async和await。
从各种渠道给出的异步编程,都是下面的方式:
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 - start
Async proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Async proccess - out Func3
Async proccess - done
Main proccess - done
咦?这个好像跟同步代码的执行结果没什么区别啊?
嗯,完全正确。上面这个代码,真的是同步执行的。
这是异步编程的第一个容易错误的理解:async和await的配对。
三、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 - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Async proccess - out Func3
Async proccess - done
Main proccess - done
Func1 proccess - end
Func2 proccess - end
Func3 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 - start
Async proccess - enter Func1
Func1 proccess - start
Func1 proccess - end
Async proccess - out Func1
Async proccess - enter Func2
Func2 proccess - start
Func2 proccess - end
Async proccess - out Func2
Async proccess - enter Func3
Func3 proccess - start
Func3 proccess - end
Async proccess - out Func3
Async proccess - done
Main proccess - done
嗯?怎么又像是同步了?
对,这是第二个容易错误的理解:await是什么意思?
四、await是什么意思
提到await,就得先说说Wait。
字面意思,Wait就是等待。
前边说了,异步有一个核心,是Task。而Task有一个方法,就是Wait,写法是Task.Wait()。所以,很多人把这个Wait和await混为一谈,这是错的。
这个问题来自于Task。C#里,Task不是专为异步准备的,它表达的是一个线程,是工作在线程池里的一个线程。异步是线程的一种应用,多线程也是线程的一种应用。Wait,以及Status、IsCanceled、IsCompleted、IsFaulted等等,是给多线程准备的方法,跟异步没有半毛钱关系。当然你非要在异步中使用多线程的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 - start
Async proccess - enter Func1
Func1 proccess - start
Async proccess - out Func1
Async proccess - done
Func1 proccess - end
Main 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的一个变形。
(全文完)
|  | 微信公众号:老王Plus 扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送 本文版权归作者所有,转载请保留此声明和原文链接 | 
一文说通C#中的异步编程的更多相关文章
- 一文说通C#中的异步编程补遗
		前文写了关于C#中的异步编程.后台有无数人在讨论,很多人把异步和多线程混了. 文章在这儿:一文说通C#中的异步编程 所以,本文从体系的角度,再写一下这个异步编程. 一.C#中的异步编程演变 1. ... 
- 一文说通C#中的异步迭代器
		今天来写写C#中的异步迭代器 - 机制.概念和一些好用的特性 迭代器的概念 迭代器的概念在C#中出现的比较早,很多人可能已经比较熟悉了. 通常迭代器会用在一些特定的场景中. 举个例子:有一个for ... 
- .Net中的异步编程总结
		一直以来很想梳理下我在开发过程中使用异步编程的心得和体会,但是由于我是APM异步编程模式的死忠,当TAP模式和TPL模式出现的时候我并未真正的去接纳这两种模式,所以导致我一直没有花太多心思去整理这两部 ... 
- C#中的异步编程Async 和 Await
		谈到C#中的异步编程,离不开Async和Await关键字 谈到异步编程,首先我们就要明白到底什么是异步编程. 平时我们的编程一般都是同步编程,所谓同步编程的意思,和我们平时说的同时做几件事情完全不同. ... 
- .NET中的异步编程——常见的错误和最佳实践
		在这篇文章中,我们将通过使用异步编程的一些最常见的错误来给你们一些参考. 背景 在之前的文章<.NET中的异步编程——动机和单元测试>中,我们开始分析.NET世界中的异步编程.在那篇文章中 ... 
- javaScript中的异步编程模式
		1.事件模型 let button = document.getElementById("my-btn"); button.onclick = function(event) { ... 
- Netty 中的异步编程 Future 和 Promise
		Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ... 
- promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和  await async 的理解
		* promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的 ... 
- .NET中的异步编程
		开篇 异步编程是程序设计的重点也是难点,还记得在刚开始接触.net的时候,看的是一本c#的Winform实例教程,上面大部分都是教我们如何使用Winform的控件以及操作数据库的实例,那时候做的基本都 ... 
随机推荐
- 探索ADC的原理(自制3位并行比较型ADC)
			摘要 本文通过列举历史中出现的产品,梳理了模数转换器在20世纪30年代~~20世纪80年代末的发展历史.接下来,简要介绍模数转换器的原理.技术指标.分类和未来发展方向.最后,提供了一种自制3位FLAS ... 
- .NET Core请求控制器Action方法正确匹配,但为何404?
			前言 有些时候我们会发现方法名称都正确匹配,但就是找不到对应请求接口,所以本文我们来深入了解下何时会出现接口请求404的情况. 匹配控制器Action方法(404) 首先我们创建一个web api应用 ... 
- cp5200的一般步骤
			cp5200的一般步骤: 1.创建数据对象 hObj = CP5200_CommData_Create(nCommType, id, GetIDCode()); 2.生成所需要的数据,如 :生成设置亮 ... 
- 【floyd+矩阵乘法】POJ 3613 Cow Relays
			Description For their physical fitness program, N (2 ≤ N ≤ 1,000,000) cows have decided to run a rel ... 
- LeetCode62. 不同路径
			由于机器人只可以向右和向下移动,所以我们要到第i行第j列,只可以由第i-1行第j列和第i行第j-1列移动一步得到,因此要到第i行第j列的方案数就是到第i-1行第j列的方案数和到第i行第j-1列的方案数 ... 
- 执行python 爬虫脚本时提示bs4.FeatureNotFound: Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?
			from bs4 import BeautifulSoupfrom urllib.request import urlopenimport re html = urlopen('http://**** ... 
- nodejs 本地压缩jpg,png图片(nodejs)
			使用nodejs实现本地压缩jpg,png图片. 使用到的包 1.images 用于压缩jpg npm install images yarn add images 2.imagemin 用于压缩 ... 
- Selenium之浏览器驱动下载和配置使用
			浏览器驱动下载 Chrome浏览器驱动:chromedriver , taobao备用地址 Firefox浏览器驱动:geckodriver Edge浏览器驱动:MicrosoftWebDriver ... 
- arm64-v8a 静态成员模板 undefined reference to
			谷歌发布新包需要64位的so Application.mk 中 APP_ABI := armeabi armeabi-v7a x86 x86_64 arm64-v8a 添加了 arm64-v8a 和 ... 
- 底层剖析 Window 、Activity、 View 三者关系
			不管工作几年的 Android 工程师,或多或少都听说过 Window 的概念,并且隐隐约约感觉它在 Activity 与 View 之间应该发挥着某种连接的作用.但是如果需要说出这 3 者之间的关系 ... 
