.net 温故知新:【5】异步编程 async await
1、异步编程
异步编程是一项关键技术,可以直接处理多个核心上的阻塞 I/O 和并发操作。 通过 C#、Visual Basic 和 F# 中易于使用的语言级异步编程模型,.NET 可为应用和服务提供使其变得可响应且富有弹性。
上面是关于异步编程的解释,我们日常编程过程或多或少的会使用到异步编程,为什么要试用异步编程?因为用程序处理过程中使用文件和网络 I/O,比如处理文件的读取写入磁盘,网络请求接口API,默认情况下 I/O API 一般会阻塞。
这样的结果是导致我们的用户界面卡住体验差,有些服务器的硬件利用率低,服务处理能力请求响应慢等问题。基于任务的异步 API 和语言级异步编程模型改变了这种模型,只需了解几个新概念就可默认进行异步执行。
现在普遍使用的异步编程模式是TAP模式,也就是C# 提供的 async 和 await 关键词,实际上我们还有另外两种异步模式:基于事件的异步模式 (EAP),以及异步编程模型 (APM) 。
APM 是基于 IAsyncResult 接口提供的异步编程,例如像FileStream类的BeginRead,EndRead就是APM实现方式,提供一对开始结束方法用来启动和接受异步结果。使用委托的BeginInvoke和EndInvoke的方式来实现异步编程。
EAP 是在 .NET Framework 2.0 中引入的,比较多的体现在WinForm编程中,WinForm编程中很多控件处理事件都是基于事件模型,经常用到跨线程更新界面的时候就会使用到BeginInvoke和Invoke。事件模式算是对APM的一种补充,定义了一系列事件包括完成、进度、取消的事件让我们在异步调用的时候能注册响应的事件进行操作。
class Program
{
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now + " start");
IAsyncResult result = BeginAPM();
//EndAPM(result);
Console.WriteLine(DateTime.Now + " end");
Console.ReadKey();
}
delegate void DelegateAPM();
static DelegateAPM delegateAPM = new DelegateAPM(DelegateAPMFun);
public static IAsyncResult BeginAPM()
{
return delegateAPM.BeginInvoke(null, null);
}
public static void EndAPM(IAsyncResult result)
{
delegateAPM.EndInvoke(result);
}
public static void DelegateAPMFun()
{
Console.WriteLine("DelegateAPMFun...start");
Thread.Sleep(5000);
Console.WriteLine("DelegateAPMFun...end");
}
}
如上代码我使用委托实现异步调用,BeginAPM 方法使用 BeginInvoke 开始异步调用,然后 DelegateAPMFun 异步方法里面停5秒。看下下面的打印结果,是 main 方法里面的打印在前,异步方法里面的打印在后,说明该操作是异步的。
其中一行代码EndAPM(result)
被注释了,调用了委托 EndInvoke 方法,该方法会阻塞程序直到异步调用完成,所以我们可以放到适当的位置用来获取执行结果,这类似于TAP模式的await 关键字,放开改行代码执行下。
以上两种方式已不推荐使用,编写理解起来比较晦涩,感兴趣的可以自行了解下,而且这种方式在.net 5里面已经不支持委托的异步调用了,所以如果要运行需要在.net framework框架下。
TAP 是在 .NET Framework 4 中引入的,是目前推荐的异步设计模式,也是我们本文讨论的重点方向,但是TAP并不一定是线程,他是一种任务,理解为工作的异步抽象,而非在线程之上的抽象。
2、async await
使用 async await 关键字可以很轻松的实现异步编程,我们子需要将方法加上 async 关键字,方法内的异步操作使用 await 等待异步操作完成后再执行后续操作。
class Program
{
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now + " start");
AsyncAwaitTest();
Console.WriteLine(DateTime.Now + " end");
Console.ReadKey();
}
public static async void AsyncAwaitTest()
{
Console.WriteLine("test start");
await Task.Delay(5000);
Console.WriteLine("test end");
}
}
AsyncAwaitTest 方法使用 async 关键字,使用await关键字等待5秒后打印"test end"。在 Main 方法里面调用 AsyncAwaitTest 方法。
使用 await 在任务完成前将控制让步于其调用方,可让应用程序和服务执行有用工作。 任务完成后代码无需依靠回调或事件便可继续执行。 语言和任务 API 集成会为你完成此操作。
使用await 的方法必须使用 async 关键字,如果我们 Main 方法里面想等待 AsyncAwaitTest 则 Main 方法需要加上 async 并返回 Task。
3、async await 原理
将上面 Main 方法不使用 await 调用的方式编译后使用ILSpy反编译dll,使用C# 4.0才能看到编译器为我们做了什么。因为4.0不支持 async await 所以会反编译到具体代码,4.0 以后的反编译后会直接显示 async await 语法。
通过反编译后可以看到在异步方法里面重新生成了一个泛型类 d__1 实现接口IAsyncStateMachine,然后调用Start方法,Start中进行了一些线程处理后调用 stateMachine.MoveNext()
即调用d__1实例化对象的MoveNext方法。
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
Thread currentThread = Thread.CurrentThread;
Thread thread = currentThread;
ExecutionContext executionContext = currentThread._executionContext;
ExecutionContext executionContext2 = executionContext;
SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
try
{
stateMachine.MoveNext();
}
finally
{
SynchronizationContext synchronizationContext2 = synchronizationContext;
Thread thread2 = thread;
if (synchronizationContext2 != thread2._synchronizationContext)
{
thread2._synchronizationContext = synchronizationContext2;
}
ExecutionContext executionContext3 = executionContext2;
ExecutionContext executionContext4 = thread2._executionContext;
if (executionContext3 != executionContext4)
{
ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
}
}
}
我们再看编译器为生成的类 <AsyncAwaitTest>d__1
:
MoveNext方法将 AsyncAwaitTest 逻辑代码包含进去了,我们的源代码因为只有一个 await 操作,如果有多个 await 操作,那么MoveNext里面应该还会有多个分段逻辑,将不同段的MoveNext放入不同的状态分段块。
在该类中也有一个if判断,按照 1__state 状态参数,最开始调用的时候是-1,执行进来 num != 0
则执行我们的业务代码if里面的,这个时候会顺序执行业务代码,直到碰到 await 则执行如下代码
awaiter = Task.Delay(5000).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<> 1__state = 0);
<> u__1 = awaiter;
< AsyncAwaitTest > d__1 stateMachine = this;
<> t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
在该过程中 <> t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
将 await 句和状态机进行传递调用 AwaitUnsafeOnCompleted
方法,该方法一直跟下去会找到线程池的操作。
// System.Threading.ThreadPool
internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool preferLocal)
{
s_workQueue.Enqueue(callBack, !preferLocal);
}
程序将封装的任务放入线程池进行调用,这个时候异步方法就切换到了另一个线程,或者在原线程上执行(如果异步方法执行时间比较短可能就不会进行线程切换,这个主要看调度程序)。
执行完成 await 后状态 1__state 已经更改了为 0,程序会再次调用 MoveNext 进入 else 之后没有return和其它逻辑,则继续执行到结束。
可以看到这是一个状态控制的执行逻辑,是一种“状态机模式”的设计模式,对于 Main 方法调用 AsyncAwaitTest 逻辑此刻进入if,碰到await则进入线程调度执行,如果异步方法切换到其它线程调用,则方法 Main 继续执行,当状态机执行切换到另外一个状态后再次 MoveNext 直到执行完异步方法。
4、async 与 线程
有了上面的基础我们知道 async 与 await 通常是成对配合使用的,当我们的方法标记为异步的时候,里面的耗时操作就需要 await 进行标记等待完成后执行后续逻辑,调用该异步方法的调用者可以决定是否等待,如果不用 await 则调用者异步执行或者就在原线程上执行异步方法。
如果 async 关键字修改的方法不包含 await 表达式或语句,则该方法将同步执行,可选择性通过 Task.Run API 显式请求任务在独立线程上运行。
可以将 AsyncAwaitTest 方法改为显示线程运行:
public static async Task AsyncAwaitTest()
{
Console.WriteLine("test start");
await Task.Run(() =>
{
Thread.Sleep(5000);
});
Console.WriteLine("test end");
}
5、取消任务 CancellationToken
如果不想等待异步方法完成,可以通过 CancellationToken 取消该任务,CancellationToken 是一个struct,通常使用 CancellationTokenSource 来创建 CancellationToken,因为CancellationTokenSource 有一些列的[方法]用于我们取消任务而不用去操作CancellationToken 结构体。
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
然我改造下方法,将 CancellationToken 传递到异步方法,cts.CancelAfter(3000)
3秒钟后取消任务,我们监听CancellationToken 如果 IsCancellationRequested==true
则直接返回 。
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
cts.CancelAfter(3000);
Console.WriteLine(DateTime.Now + " start");
AsyncAwaitTest(ct);
Console.WriteLine(DateTime.Now + " end");
Console.ReadKey();
}
public static async Task AsyncAwaitTest(CancellationToken ct)
{
Console.WriteLine("test start");
await Task.Delay(5000);
Console.WriteLine(DateTime.Now + " cancel");
if (ct.IsCancellationRequested) {
return;
}
//ct.ThrowIfCancellationRequested();
Console.WriteLine("test end");
}
因为我们是手动通过代码判断状态结束异步,所以即使在3秒后就已经结束了任务,但是await Task.Delay(5000)
任然会等待5秒执行完。还有一种方式就是我们不判断是否取消,直接调用ct.ThrowIfCancellationRequested()
给我们判断,这个方法如果,但是任然不能及时结束。这个时候我们还有另外一种处理方式,就是将CancellationToken 传递到 await 的异步API方法里,可能会立即结束,也可能不会,这个要取决异步实现。
public static async Task AsyncAwaitTest(CancellationToken ct)
{
Console.WriteLine("test start");
//传递CancellationToken 取消
await Task.Delay(5000,ct);
Console.WriteLine(DateTime.Now + " cancel");
//手动处理取消
//if (ct.IsCancellationRequested) {
// return;
//}
//调用方法处理取消
//ct.ThrowIfCancellationRequested();
Console.WriteLine("test end");
}
6、注意项
在异步方法里面不要使用 Thread.Sleep 方法,有两种可能:
1、Sleep在 await 之前,则会直接阻塞调用方线程等待Sleep。
2、Sleep在 await 之后,但是 await 执行在调用方的线程上也会阻塞调用方线程。
所以我们应该使用 Task.Delay 用于等待操作。那为什么我上面的 Task.Run 里面使用了 Thread.Sleep呢,因为 Task.Run 是显示请求在独立线程上运行,所以我知道这里写不会阻塞调用方,上面我只是为了演示,所以不建议用。
.net 温故知新:【5】异步编程 async await的更多相关文章
- 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法
什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...
- 异步编程Async/await关键字
异步编程Async \await 关键字在各编程语言中的发展(出现)纪实. 时间 语言版本 2012.08.15 C#5.0(VS2012) 2015.09.13 Python 3.5 2016.03 ...
- 抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext
长话短说,本文带大家抓住异步编程async/await语法糖的牛鼻子: SynchronizationContext 引言 C#异步编程语法糖async/await,使开发者很容易就能编写异步代码. ...
- javascript异步编程 Async/await
Async/await Async/await 在学习他之前应当补充一定的 promise 知识 它是一种与 promise 相配合的特殊语法,目前被认为是异步编程的终级解决方案 值得我们每一个人学习 ...
- [C#] 谈谈异步编程async await
为什么需要异步,异步对可能起阻止作用的活动(例如,应用程序访问 Web 时)至关重要. 对 Web 资源的访问有时很慢或会延迟. 如果此类活动在同步过程中受阻,则整个应用程序必须等待. 在异步过程中, ...
- .net 异步编程async & await关键字的思考
C# 5.0引入了两个关键字 async和await,这两个关键字在很大程度上帮助我们简化了异步编程的实现代码,而且TPL中的task与async和await有很大的关系 思考了一下异步编程中的asy ...
- 异步编程async/await
什么是异步? 在异步程序中,程序代码不需要按照编写时的顺序严格执行,有时需要一在一个新的线程中运行一部分代码,有时无需创建新的 线程,但是为了更好的利用单个线程的能力,需要改变代码的执行顺序. 进程 ...
- c#异步编程async await
可以代替协程了 但是需要.net4 版本 unity2017以上版本可以用了 再也可以不用蛋疼的没有返回值的协程了 //异步编程,和Task一起用 async void TestAsync(){ // ...
- .NetCore 异步编程 - async/await
前言: 这段时间开始用.netcore做公司项目,发现前辈搭的框架通篇运用了异步编程方式,也就是async/await方式,作为一个刚接触的小白,自然不太明白其中原理,最重要的是,这个玩意如果不明白基 ...
随机推荐
- Arduino IDE 2.0 beta安装
1.在官网(Software | Arduino)下载安装包,此次提供操作系统有:Windows.Linux和macOC系统 2.点击安装包进行安装 3.点击我同意 4.点击下一步 5.选择安装路径( ...
- mongodb oplog详解和格式分析
1. 基本概念 oplog使用固定大小集合记录了数据库中所有修改操作的操作日志(新增.修改和删除,无查询),mongodb收到修改请求后,先在主节点(Primary)执行请求,再把操作日志保存到opl ...
- ti
一.选择题DCBCDCDACAACBBABACBDCBBDA二.简答题(每小题5分,共20分)1. 1)简洁紧凑,灵活方便2)运算符丰富3)数据类型丰富4)C语言是结构化语言5)语法限制较少,程序设计 ...
- 安卓源码默认开启USB调试
找到\frameworks\base\services\usb\java\com\android\server\usb\UsbDeviceManager.java下的 Settings.Global. ...
- Motion Planning 是什么
前言与引用 这一个呢,主要是自己突然看一篇论文的时候不知道 为什么他提出的方法对于规划来说就是好的,规划又应该分为哪几个部分,解决的是哪几个部分的问题?带着这个问题,我就去搜:Motion Plann ...
- Hive——简介
Hive--简介 Hive 是基于 Hadoop 构建的一套数据仓库分析系统,它提供了丰富的 SQL 查询方式来分析存储在 Hadoop 分布式文件系统中的数据, 可以将结构化的数据文件映射为一张数据 ...
- Linux CentOS 7 安装配置vsftp
学习Linux时间不长,首次安装了vsftp,按照网上的各种帖子尝试配置,不过都没打到预期,不是被拒绝连接,就是连接超时,总之就是各种问题啊.当然了,不是别人配置的不对,而是自己不是太懂Linux,选 ...
- QLabel的使用
现在学习一个简单的控件Label. 第一步:打开designer.exe.拖动一个控件到主窗口,双击可以编辑文字. 第二步:设置字体大小 第三步:设置文字颜色 第四步:设置背景色 第五步:将文字居中 ...
- 痞子衡嵌入式:深扒i.MXRTxxx系列ROM中集成的串行NOR Flash启动SW Reset功能及其应用场合
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRTxxx系列ROM中集成的串行NOR Flash启动SW Reset功能及其应用场合. 在串行 NOR Flash 热启动过程 ...
- css--实现一个文字少时居中,文字换行时居左的样式
前言 最近群里的小伙伴去面试,遇到这样一个问题,面试官问:"用 css 对一行文字进行布局,当文字不够换行的时候,这行文字要居中显示,当文字出现换行的时候,这行文字要靠左显示.", ...