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.WaitTask<T>.Result 方法,因为它们会导致死锁。如果使用了 async ,最好就一直使用它。
  • 异步方法的参数不能使用outrefoutref 返回的数据应借用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 的异步方法的调用方无法捕获从该方法引发的异常,且此类未经处理的异常可能会导致应用程序故障。 如果返回 TaskTask<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#并发编程经典实例》学习笔记—异步编程关键字 Async和Await的更多相关文章

  1. 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换

    [源码下载] 重新想象 Windows 8 Store Apps (44) - 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换 作者:webabcd 介绍 ...

  2. 《C#并发编程经典实例》笔记

    1.前言 2.开宗明义 3.开发原则和要点 (1)并发编程概述 (2)异步编程基础 (3)并行开发的基础 (4)测试技巧 (5)集合 (6)函数式OOP (7)同步 1.前言 最近趁着项目的一段平稳期 ...

  3. nodejs学习笔记 —— 异步编程解决方案

    在js或者node编程中,由于异步的频繁和广度使用,使得回调和嵌套的深度导致编程的体验遇到一些挑战,如果写出优雅和好看的代码,本文主要针对异步编程的主流方案做一些总结 1.事件发布/订阅模式 事件监听 ...

  4. 09-Node.js学习笔记-异步编程

    同步API,异步API 同步API:只有当前API执行完成后,才能继续执行下一个API console.log('before'); console.log('after'); 异步API:当前API ...

  5. 多线程之异步编程: 经典和最新的异步编程模型,async与await

    经典的异步编程模型(IAsyncResult) 最新的异步编程模型(async 和 await) 将 IAsyncInfo 转换成 Task 将 Task 转换成 IAsyncInfo 示例1.使用经 ...

  6. 多线程之异步编程: 经典和最新的异步编程模型, IAsyncInfo 与 Task 相互转换

    经典的异步编程模型(IAsyncResult) 最新的异步编程模型(async 和 await) 将 IAsyncInfo 转换成 Task 将 Task 转换成 IAsyncInfo 示例1.使用经 ...

  7. 《C#并发编程经典实例》学习笔记—2.7 避免上下文延续

    避免上下文延续 在默认情况下,一个 async 方法在被 await 调用后恢复运行时,会在原来的上下文中运行. 为了避免在上下文中恢复运行,可让 await 调用 ConfigureAwait 方法 ...

  8. 《C#并发编程经典实例》学习笔记—3.1 数据的并行处理

    问题 有一批数据,需要对每个元素进行相同的操作.该操作是计算密集型的,需要耗费一定的时间. 解决方案 常见的操作可以粗略分为 计算密集型操作 和 IO密集型操作.计算密集型操作主要是依赖于CPU计算, ...

  9. 《C#并发编程经典实例》学习笔记—2.3 报告任务

    问题 异步操作时,需要展示该操作的进度 解决方案 IProgress<T> Interface和Progress<T> Class 插一段话:读<C#并发编程经典实例&g ...

随机推荐

  1. 功能强大的js数组方法:reduce

    arr.reduce()方法接受一个函数作为累加器,数组中的每个值从左到右开始缩减,最终为一个值. reduce接受的参数主要有callback(回调函数)和可选参数initvalue(作为第一次调用 ...

  2. vue数据双向绑定

    Vue的双向绑定是通过数据劫持结合发布-订阅者模式实现的,即通过Object.defineProperty监听各个属性的setter,然后通知订阅者属性发生变化,触发相应的回调. 整个过程分为以下几步 ...

  3. Openvswitch手册(7): Interfaces

    我们来看Interfaces ofport: OpenFlow port number for this interface. type: system: An ordinary network de ...

  4. 修复运行 tasklist 命令时提示 ERROR: Not found

    最近碰到了一个脚本运行 tasklist 总是提示 ERROR: Not found,在这里找到了修复的方法 https://superuser.com/questions/1282867/windo ...

  5. Redis安装和实际应用

    上次介绍了Redis的来龙去脉以及相关一些情况,点击回顾<深入浅出Redis>,接下来我们再讲讲Redis的安装和实际应用. 一.Redis的安装 下载安装包,redis-3.2.9.ta ...

  6. Android Studio升级3.2.1后的合并XML出错的解决方案

    升级到3.2.1版本之后,遇到了合并XML出错的问题.错误内容大概如下: 当大家看到这个问题的时候,可能会有一行是可以点击的文件目录,点击到报错的地方. 如果没有可以点击的地方,也可以根据目录和 “行 ...

  7. 腾讯开源 MMKV — 基于mmap的高性能通用key-value组件

    一.介绍 MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强.从 2015 年中至今,在 iOS 微信上使用已有近 3 ...

  8. FragmentTabHost用法

    FragmentTabHost组成 Tabhost,TabWidget,切换的内容容器FrameLayout 层级关系 ----FragmentTabHost |-----TabWidget |--- ...

  9. IDEA中使用lombok插件

    Lombok是什么? lombok是一个可以通过简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 Java 代码的工具,简单来说,比如我们新建了一个类,然后在其中写了几个字段,然后通常情况下 ...

  10. Pycharm Debug调试心得

    1.操作步骤: 1-1.添加断点:直接在标记处点击鼠标左键即可.(删除断点只需再点击断点处即可) 1-2.Debug下运行代码 1-3.按照所需调试进行代码调试.Debug的调试方式如下所示: 分别为 ...