在用async包裹的方法体中,可以使用await关键字以同步的方式编写异步调用的代码。那么它的内部实现原理是什么样的呢?我们是否可以自定义await以实现定制性的需求呢?先来看一个简单的例子:

     class Test {
public static void Main (string[] args) {
Task.Run (new Func<Task<string>>(task1));
Console.ReadLine ();
} private async static Task<string> task1() {
string ret = await task2 ();
Console.WriteLine ("Await Task Result:" + ret);
return ret;
} private static Task<string> task2() {
return Task.FromResult<string> ("Task2");
}
}

通过ILSpy反编译(要关闭"视图-选项-反编译await/async"菜单项),得到如下代码:

     internal class Test
{
[CompilerGenerated]
[StructLayout(LayoutKind.Auto)]
private struct <task1>d__0 : IAsyncStateMachine
{
public int <>1__state;
public AsyncTaskMethodBuilder<string> <>t__builder;
public string <ret>5__1;
private TaskAwaiter<string> <>u__$awaiter2;
private object <>t__stack; void IAsyncStateMachine.MoveNext()
{
string result;
try
{
int num = this.<>1__state;
if (num != -)
{
TaskAwaiter<string> taskAwaiter;
if (num != )
{
taskAwaiter = Test.task2().GetAwaiter();
if (!taskAwaiter.IsCompleted)
{
this.<>1__state = ;
this.<>u__$awaiter2 = taskAwaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Test.<task1>d__0>(ref taskAwaiter, ref this);
return;
}
}
else
{
taskAwaiter = this.<>u__$awaiter2;
this.<>u__$awaiter2 = default(TaskAwaiter<string>);
this.<>1__state = -;
}
string arg_86_0 = taskAwaiter.GetResult();
taskAwaiter = default(TaskAwaiter<string>);
string text = arg_86_0;
this.<ret>5__1 = text;
Console.WriteLine("Await Task Result:" + this.<ret>5__1);
result = this.<ret>5__1;
}
}
catch (Exception exception)
{
this.<>1__state = -;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -;
this.<>t__builder.SetResult(result);
} [DebuggerHidden]
void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
{
this.<>t__builder.SetStateMachine(param0);
}
} public static void Main(string[] args)
{
Task.Run<string>(new Func<Task<string>>(Test.task1));
Console.ReadLine();
} [DebuggerStepThrough, AsyncStateMachine(typeof(Test.<task1>d__0))]
private static Task<string> task1()
{
Test.<task1>d__0 <task1>d__;
<task1>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
<task1>d__.<>1__state = -;
AsyncTaskMethodBuilder<string> <>t__builder = <task1>d__.<>t__builder;
<>t__builder.Start<Test.<task1>d__0>(ref <task1>d__);
return <task1>d__.<>t__builder.Task;
} private static Task<string> task2()
{
return Task.FromResult<string>("Task2");
}
}

按照代码的调用顺序,我们关注下task1()的内部实现。

首先是初始化结构体<task1>d_0的实例<task1>d__。那么<task1>d__0是个什么东东呢?由编译器的生成代码中可以看到,它是一个实现了IAsyncStateMachine接口的结构体,而用户代码则被编译器重新组织进了MoveNext()方法中。<task1>d__0有个内部状态成员<>1__state,MoveNext()方法根据这个状态调转到相应的代码块中加以执行。

了解了<task1>d__0的声明实现,再看下task1()方法中的具体调用。在创建实例<task1>d__之后,设置初始状态<>1__state为-1,并调用<>t__builder的Start方法。不难推断,在Start方法中会调用<task1>d__.MoveNext(),此时内部状态为-1,会先调用Test.task2().GetAwaiter()获取其所关联的TaskAwaiter实例。如果awaiter当前是未结束的话,则设置<>1__state为0,并将当前<task1>d__作为参数关联到TaskAwaiter实例的onCompletedContinuation回调延续中去。当未来某个时刻,TaskAwaiter所关联的Task任务结束时,会设置awaiter的异步结果并触发回调延续,导致调用<task1>d__.MoveNext()方法,并最终跳转到用户代码块中,获取awaiter的异步结果并交由用户代码处理。这个回调,基于Task.ConfigureAwait(true/false)的不同,会在后续切换到当前线程或是从线程池中取了一个空闲线程来处理(更细节可参考.net源码分析)。

这里要顺便提一句,在本例中,通过Task.Run创建了taskX1,await之后的代码与taskX1没有任何关系,从编译器生成的代码来看,在调用task1()方法并调用<task1>d__.Start()方法之后taskX便结束了,虽然task1()方法返回了新的Task<string>实例,但是只是特定类型的返回值而已,与taskX1或Task没有任何关系。

由以上分析可以看到,async/await只是一个语法糖,async告知编译器要生成状态机代码,await则是配合生成GetAwaiter(),并封装跳转的用户代码块。除此之外,async/await与Task没有任何直接关系。而TaskAwaiter的作用,是实现INotifyCompletion(在System.Runtime.CompilerServices命名空间)以桥接异步回调过程。那么第二个自定义await的问题便一目了然了:任何类型,只需要实现GetAwaiter()方法以返回INotifyCompletion实例,便可以被await。

举个例子:

     class TestAwaiter<T> : INotifyCompletion {
private T result;
private Action continuation; // INotifyCompletion Implement
public void OnCompleted(Action continuation) { this.continuation = continuation; } // Compiler Call Methods
public bool IsCompleted { get; private set; }
public T GetResult() { return result; }
public TestAwaiter<T> GetAwaiter() { return this; } // Self Call Methods
public void SetResult(T ret) {
result = ret;
if (continuation != null) {
continuation ();
}
}
} class Test {
public static void Main (string[] args) {
Task.Run (new Action(task1));
Console.ReadLine ();
} private async static void task1() {
Console.WriteLine ("Begin await:");
int ret = await testAwaiter ();
Console.WriteLine ("Await Task Result:" + ret);
} private static TestAwaiter<int> testAwaiter() {
TestAwaiter<int> awaiter = new TestAwaiter<int> ();
ThreadPool.QueueUserWorkItem (_ => {
Thread.Sleep();
awaiter.SetResult ();
});
return awaiter;
}
}

这里没有再定义单独的类型以返回TestAwaiter,而是把二者都封装在了TestAwaiter内部。运行结果如下:

Begin await:

Await Task Result:100

async-await原理解析的更多相关文章

  1. async/await方法解析

    欲了解await,必须先了解Promise,可参考: http://www.cnblogs.com/yanze/p/6347646.html 支持度: ES6已支持Promise,ES7也决定支持aw ...

  2. 进阶篇:以IL为剑,直指async/await

    接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理. 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅的 ...

  3. 从C#到TypeScript - async await

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  4. promise async await使用

    1.Promise (名字含义:promise为承诺,表示其他手段无法改变) Promise 对象代表一个异步操作,其不受外界影响,有三种状态: Pending(进行中.未完成的) Resolved( ...

  5. 温故知新,CSharp遇见异步编程(Async/Await),聊聊异步编程最佳做法

    什么是异步编程(Async/Await) Async/Await本质上是通过编译器实现的语法糖,它让我们能够轻松的写出简洁.易懂.易维护的异步代码. Async/Await是C# 5引入的关键字,用以 ...

  6. .net 温故知新:【5】异步编程 async await

    1.异步编程 异步编程是一项关键技术,可以直接处理多个核心上的阻塞 I/O 和并发操作. 通过 C#.Visual Basic 和 F# 中易于使用的语言级异步编程模型,.NET 可为应用和服务提供使 ...

  7. How Javascript works (Javascript工作原理) (四) 事件循环及异步编程的出现和 5 种更好的 async/await 编程方式

    个人总结: 1.讲解了JS引擎,webAPI与event loop合作的机制. 2.setTimeout是把事件推送给Web API去处理,当时间到了之后才把setTimeout中的事件推入调用栈. ...

  8. async/Await使用和原理

    await/async是.NetFramework4.5出现的,是语法糖,由编译器提供的功能! await/async 是C#保留关键字,通常是成对出现,一般的建议是:要么不用,要么用到底 async ...

  9. Atitit. Async await 优缺点 异步编程的原理and实现 java c# php

    Atitit. Async await 优缺点 异步编程的原理and实现 java c# php 1. async & await的来源1 2. 异步编程history1 2.1. 线程池 2 ...

  10. “setTimeout、Promise、Async/Await 的区别”题目解析和扩展

    解答这个题目之前,先回顾下JavaScript的事件循环(Event Loop). JavaScript的事件循环 事件循环(Event Loop):同步和异步任务分别进入不同的执行"场所& ...

随机推荐

  1. 使用MYSQL命令直接导入导出SQL文件(转)

    参考:http://blog.csdn.net/jiary5201314/article/details/52026816 1.MYSQL中将数据库导出成SQL文件 其实很简单的,就是一条语句就可以了 ...

  2. spring boot--常用配置

    spring boot 需要引用的依赖项: spring-boot-starter-parent // 所有Spring Boot组件的基础引用 spring-boot-starter-web // ...

  3. hdu 4883

    简单题,当时竟然没有敲出来╮(╯▽╰)╭... 方法:每个时间点排序从小到大排序,之后扫一遍即可:是进的时间点就加人,反之出人.更新最大值即可....囧... #include<iostream ...

  4. 自定义table样式

    .tableWrap { width: 100%; border-collapse:collapse; border-top:1px solid #e9e9e9; border-left:1px so ...

  5. webstorm调试(一)提示css未使用的选择器Selector is never used

    一.css未使用的选择器Selector 今天写vue的时候,给动态绑定了一个class属性,然后样式里面就给了warning,看起来怪怪的,很不舒服

  6. T9270 mjt树

    题目背景 从前森林里有一棵很大的mjt树,树上有很多小动物. 题目描述 mjt树上有 n 个房间,第 i 个房间住着 ai 只第bi 种小动物. 这n个房间用n-1条路连接起来,其中房间1位mjt树的 ...

  7. bzoj2555(lct维护sam)

    题意: (1):在当前字符串的后面插入一个字符串 (2):询问字符串s在当前字符串中出现了几次?(作为连续子串) 字符串长度<=6e5,询问总长度<=3e6 分析: 考虑建个sam,然后把 ...

  8. android CheckBox使用和状态获得

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools=&q ...

  9. Java笔试面试题007

    Java笔试面试题007 1.请用正則表達式匹配出QQ号(如果QQ号码为5-10位). 解答: ^ \d{5,10}$ 2.String, StringBuffer StringBuilder的差别. ...

  10. [CSS3] Define Form Element States with CSS Form Pseudo Classes

    Using just semantic CSS Pseudo-Classes you can help define important states for form elements that e ...