《C#并发编程经典实例》学习笔记—异步编程关键字 Async和Await
c# 5.0 推出async和await,最早是.net framework 4.5引入,可以在visual studio 2012使用。在此之前的异步编程实现难度较高,async使异步编程的实现变得简便。
各平台对async的支持情况
平台 | async |
---|---|
.net 4.5及以上 | ✓ |
.net 4.0 | nuget |
mono ios/droid | ✓ |
windows store | ✓ |
windows phone apps 8.1 | ✓ |
windows phone sl 8.0 | ✓ |
windows phone sl 7.1 | nuget |
silverlight 5 | nuget |
在不支持的平台,安装nuget包 microsoft.bcl.async
使用
async
修饰符可将方法、lambda 表达式或匿名方法指定为异步。
async 对方法做了什么处理
从使用async修饰符修饰的方法的il代码可以得出一个结论:
- 在debug下,针对async方法,生成的是一个class状态机
- 在release下,针对async方法,生成的是一个struct状态机
举例:
c#代码如下
using system.threading.tasks; namespace consoleapp3 { public class test { public async task testasync() { await getasync(); } public async task getasync() { await task.delay(1); } } }
以testasync方法为准
release下 初始化状态机v_0
,类型是值类型struct(valuetype
),类型名称为<testasync>d__0
.locals init ( [0] valuetype consoleapp3.test/'<testasync>d__0' v_0, [1] valuetype [mscorlib]system.runtime.compilerservices.asynctaskmethodbuilder v_1 )
<testasync>d__0
继承值类型[mscorlib]system.valuetype
.class nested private sealed auto ansi beforefieldinit '<testasync>d__0' extends [mscorlib]system.valuetype implements [mscorlib]system.runtime.compilerservices.iasyncstatemachine
debug 下 初始化状态机v_0
,类型是引用类型class(class
) ,类型名称为<testasync>d__0
.locals init ( [0] class consoleapp3.test/'<testasync>d__0' v_0, [1] valuetype [mscorlib]system.runtime.compilerservices.asynctaskmethodbuilder v_1 )
<testasync>d__0
继承引用类型[mscorlib]system.object
.class nested private sealed auto ansi beforefieldinit '<testasync>d__0' extends [mscorlib]system.object implements [mscorlib]system.runtime.compilerservices.iasyncstatemachine
异步方法的定义和注意事项
使用
async
关键字定义的异步方法简称为“异步方法”。
注意事项:
- 如果
async
关键字修改的方法不包含await
表达式或语句,则该方法将同步执行。 编译器警告将通知你不包含await
语句的任何异步方法,因为该情况可能表示存在错误。 请参阅编译器警告(等级 1)cs4014。 -
async
关键字是上下文关键字,原因在于只有当它修饰方法、lambda 表达式或匿名方法时,它才是关键字。 在所有其他上下文中,都会将其解释为标识符。 - 不要用
void
作为async
方法的返回类型!async
方法可以返回void
,但是这仅限于编写事件处理程序。一个普通的async
方法如果没有返回值,要返回task
,而不是void
。 - 一定要避免使用
task.wait
或task<t>.result
方法,因为它们会导致死锁。如果使用了async
,最好就一直使用它。 - 异步方法的参数不能使用
out
,ref
。out
或ref
返回的数据应借用task<tresult>
返回,可以使用元组或自定义数据结构。
异步方法的特征
- 方法签名包含
async
修饰符。 - 按照约定,异步方法的名称以“async”后缀结尾。
- 返回类型为下列类型之一:
- 如果你的方法有操作数为
tresult
类型的返回语句,则为task<tresult>
。 - 如果你的方法没有返回语句或具有没有操作数的返回语句,则为
task
。 -
void
:如果要编写异步事件处理程序。 - 包含
getawaiter
方法的其他任何类型(自 c# 7.0 起)。
- 如果你的方法有操作数为
- 方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控制返回到方法的调用方。
关于async和await具体的执行流程,方法何时挂起和释放,请参考异步程序中的控制流 (c#)
异步返回类型
上面提到 void
作为返回结果,适用于事件处理程序。
举例:
using system; using system.threading.tasks; namespace consoleapp3 { public class testvoidasync { private event eventhandler<eventargs> dotest; public testvoidasync() { dotest += dotestevent; } private static async void dotestevent(object sender, eventargs e) { await task.delay(1000); } protected virtual void ondotest() { dotest?.invoke(this, eventargs.empty); } } }
void
作为返回结果存在一个弊端:无法捕获异常。
返回 void 的异步方法的调用方无法捕获从该方法引发的异常,且此类未经处理的异常可能会导致应用程序故障。 如果返回
task
或task<tresult>
的异步方法中出现异常,此异常将存储于返回的任务中,并在等待该任务时再次引发。
通用的异步返回类型:
从 c# 7.0 开始,异步方法可返回任何具有可访问的 getawaiter 方法的类型。
valuetask<tresult>
:
task 和 task<tresult> 是引用类型
,因此,性能关键路径中的内存分配会对性能产生负面影响,尤其当分配出现在紧凑循环中时。 支持通用返回类型意味着可返回轻量值类型(而不是引用类型),从而避免额外的内存分配。
使用valuetask<tresult>
,需要添加nuget包 system.threading.tasks.extensions
。
valuetask<tresult>
是struct值类型,task 和 task<tresult>
是class引用类型
异步操作的生命周期
task 类提供了异步操作的生命周期,且该周期由 taskstatus 枚举表示。
状态 | 执行顺序 | 备注 |
---|---|---|
created | 0 | 该任务已初始化,但尚未安排。 |
waitingforactivation | 1 | 该任务正在等待被.net framework infrastructure 内部激活和调度。 |
waitingtorun | 2 | 该任务已安排执行但尚未开始执行。 |
running | 3 | 任务正在运行但尚未完成。 |
waitingforchildrentocomplete | 4 | 任务已完成执行,并隐式等待附加的子任务完成。 |
rantocompletion | 5 | 任务已成功完成执行。 |
canceled | 6 | 引发 operationcanceledexception 异常,或者在任务开始执行之前取消 |
faulted | 7 | 由于未处理的异常,任务已完成。 |
canceled 和 faulted状态都会因为任务异常导致转换为该状态。二者的区别如下:
如果标记的 iscancellationrequested 属性返回 false,或者异常的标记与任务的标记不匹配,则会将 operationcanceledexception 按照普通的异常来处理,从而导致任务转换为 faulted 状态。 另外还要注意,其他异常的存在将也会导致任务转换为 faulted 状态。 您可以在 status 属性中获取已完成任务的状态。
参考文章:
推荐阅读
-
《C#并发编程经典实例》学习笔记—2.2 返回完成的任务
-
《C#并发编程经典实例》学习笔记-进程(process)和线程(thread)
-
《C#并发编程经典实例》学习笔记—2.9 处理 async void 方法的异常
-
《C#并发编程经典实例》学习笔记—2.8 处理 async Task 方法的异常
-
《C#并发编程经典实例》学习笔记-第一章并发编程概述
-
《C#并发编程经典实例》学习笔记-第一章并发编程概述
-
《C#并发编程经典实例》学习笔记—2.5 等待任意一个任务完成 Task.WhenAny
-
《C#并发编程经典实例》学习笔记—2.3 报告任务
-
《C#并发编程经典实例》学习笔记—2.7 避免上下文延续
-
《C#并发编程经典实例》学习笔记—2.1 暂停一段时间