在async和await之前我们用Task来实现异步任务是这样做的:

static Task<string> GetBaiduHtmlTAP()
{
      //创建一个异步Task对象,内部封装了异步任务逻辑
return new Task<string>(() =>
{
Console.WriteLine("GetBaiduHtml 1:" + Thread.CurrentThread.ManagedThreadId); var httpRequest = WebRequest.CreateHttp("http://www.baidu.com");
var response = httpRequest.GetResponse(); Console.WriteLine("GetBaiduHtml 2:" + Thread.CurrentThread.ManagedThreadId);
//var response = httpRequest.GetResponse();
var streamReader = new StreamReader(response.GetResponseStream());
//var bodyText = await streamReader.ReadToEndAsync();
var bodyText = streamReader.ReadToEnd();
//Console.WriteLine(bodyText);
return bodyText;
});
}
这样来调用:

var task = GetBaiduHtmlTAP();
task.Start();//在线程池中启动异步任务
task.Wait();//等待异步任务结束,此时当前线程阻塞在这里
Console.WriteLine(task.Result);//获取异步任务结果。

而使用async和await后的异步任务是这样写的:

static async Task<string> GetBaiduHtmlAsync()
{
Console.WriteLine("GetBaiduHtml 1:" + Thread.CurrentThread.ManagedThreadId); var httpRequest = WebRequest.CreateHttp("http://www.baidu.com");
var response = await httpRequest.GetResponseAsync();
          
//通过查看线程id,await之后的代码是在一个新的线程中运行的。也就是说当调用await的时候,当前线程会退出执行,await的task完成后在新的线程运行后续代码
Console.WriteLine("GetBaiduHtml 2:" + Thread.CurrentThread.ManagedThreadId); 
var streamReader = new StreamReader(response.GetResponseStream());
var bodyText = streamReader.ReadToEnd(); return bodyText; } 调用的时候这样做:

string result = await GetBaiduHtmlAsync();
      Console.WriteLine(result);

 

其实不仅仅是写法变了,而是执行原理,控制权模型也发生了变化。引用官方的图:

上图第6步执行完await后,线程的控制权就返回给调用者了,而不是阻塞。详见

在使用中有如下问题一直很疑惑。

1. 为什么async方法的返回值是Task对象,但是我们在方法体内部返回的却可以是Task的Result对象呢。(唯一能解释该问题的只能是编译器针对async方法特殊处理了。)

2. 为什么如果要在方法中使用await,方法必须要标示为async

3. 一般我们自己创建的Task对象需要手动去调用run方法才能启动任务。那async方法返回的Task对象为什么不需要手动调用run方法task所封装的代码就运行了呢。(一开始是以为对task执行await关键字的时候会调用run方法,但是其实就算不执行await,async方法返回的task对象就已经是在运行状态了。)

4. 当我们await异步任务后,为什么异步任务结束之后可以在新的线程中继续运行await之后的代码

5. 通过测试可以知道,GetBaiduHtmlAsync方法中await调用前面的代码是在函数调用者的线程中运行,而await调用后面的代码是在另一个线程中运行。

如果编译器是将方法体内部整个在一个Task中运行,那await调用前面的代码不应该在函数调用者线程中运行。

要解释这些问题,最直接的办法就是查看编译器生成的代码。

问题1          

把上面的async函数代码编译后,再通过反编译工具反编译得到如下结果:

通过图片我们可以看到:

1. 生成了一个新的类:<GetBaiduHtmlAsync>d_2。

2. 而GetBaiduHtmlAsync方法则被替换为编译器产生的代码。

先看看这个编译器重写的方法的签名:没有了async关键字,返回值为Task<string>,而在方法体里面返回的是:d__.<>t_builder.Task,并且在返回值前调用了<>t_builder的Start方法。

通过这段代码可以回答上面第1,3个问题了。

async方法里不用返回Task对象确实是编译器的语法糖,编译器会为我们生成返回Task对象的代码。至于Task的状态,我们可以继续看上面生成的代码:

新生成的类<GetBaiduHtmlAsync>d_2实现了IAsyncStateMachine接口,可以认为是一个状态机。而其中的属性<>t__builder的类型是AsyncTaskMethodBuilder<T>,MSDN上对它的描述是

Represents a builder for asynchronous methods that returns a task and provides a parameter for the result

也就是说提供将异步方法转换为Task的功能,再看看它的Start方法的定义:

public void Start<TStateMachine>(
ref TStateMachine stateMachine
)
where TStateMachine : IAsyncStateMachine Begins running the builder with the associated state machine.

理解为基于传入的状态机启动有Builder产生的Task。

因此上面编译器生成的GetBaiduHtmlAsync方法的所做的事情是:

1. 实例化一个状态机<GetBaiduHtmlAsync>d_2,并将状态设置为-1(我们自己所定义的GetBaiduHtmlAsync方法体应该被移入这个状态机种,后面描述)

2. 创建了一个AsyncTaskMethodBuilder<string>实例来产生异步任务的Task对象

3. 基于Step1中创建的状态机启动Step2中创建的builder所生成的Task。

4. 将Task对象返回给调用者

至此可以解释上面第3个问题。Task在返回给调用者时已经被启动。但是发现一个问题,GetBaiduHtmlAsync方法返回的task对象的status属性并不是Running状态,而是WaitingForActivation。貌似第3个问题并不是我想象的那样。接着往下看。

问题4        

那我们自己定义的GetBaiduHtmlAsync中的代码去哪了呢?很明显应该被转换为状态机,被移植到<GetBaiduHtmlAsync>d_2类中了。下面是它的代码:

private struct <GetBaiduHtmlAsync>d__2 : IAsyncStateMachine
{
// Fields
public int <>1__state;
public AsyncTaskMethodBuilder<string> <>t__builder;
private TaskAwaiter<WebResponse> <>u__1; // Methods
private void MoveNext()
{
string str;
int num = this.<>1__state;
try
{
TaskAwaiter<WebResponse> awaiter;
if (num != )
{
Console.WriteLine("GetBaiduHtml 1:" + Thread.CurrentThread.ManagedThreadId);
awaiter = WebRequest.CreateHttp("http://www.baidu.com").GetResponseAsync().GetAwaiter();
if (!awaiter.IsCompleted)
{
this.<>1__state = num = ;
this.<>u__1 = awaiter;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<WebResponse>, Program.<GetBaiduHtmlAsync>d__2>(ref awaiter, ref this);
return;
}
}
else
{
awaiter = this.<>u__1;
this.<>u__1 = new TaskAwaiter<WebResponse>();
this.<>1__state = num = -;
}
WebResponse result = awaiter.GetResult();
awaiter = new TaskAwaiter<WebResponse>();
Console.WriteLine("GetBaiduHtml 2:" + Thread.CurrentThread.ManagedThreadId);
str = new StreamReader(result.GetResponseStream()).ReadToEnd();
}
catch (Exception exception)
{
this.<>1__state = -;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -;
this.<>t__builder.SetResult(str);
} [DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
this.<>t__builder.SetStateMachine(stateMachine);
}
}

这里实现的大致逻辑为:

1. 状态机初始状态为-1,因此上面的builder启动任务时会创建获取Http的Response的异步Task

2. 设置状态机状态为0,表示在等待http task的结果。这里有个关键调用

3. 当http task任务结束,获取http task的result,从中读取http response的body,同时将状态机状态设为-1

4. 将http response的body设置builder关联的task的result,设置状态机状态为-2

上面的过程中有一个关键调用:

this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<WebResponse>, Program.<GetBaiduHtmlAsync>d__2>(ref awaiter, ref this);
这个调用会去等待awaiter,awaiter完成后会调用状态机的moveNext方法将状态机移到下一个状态。这个调用推动了上面状态机的转换。

从上面的逻辑可以看出,编译器将我们的代码以await调用为分隔生成了一个状态机,await调用前的代码置于状态-1中,await调用后的代码置于状态0中。通过这种方式来实现异步任务完成后继续运行await后面
的代码。至此解释了上面的第4个问题。其实和回调效果一样,只不过使用await是代码可读性更好。 问题3,5         
接着我们再看前面提到过的AsyncTaskMethodBuilder<T>的Start方法:
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine: IAsyncStateMachine
{
if (((TStateMachine) stateMachine) == null)
{
throw new ArgumentNullException("stateMachine");
}
ExecutionContextSwitcher ecsw = new ExecutionContextSwitcher();
RuntimeHelpers.PrepareConstrainedRegions();
try
{
ExecutionContext.EstablishCopyOnWriteScope(ref ecsw);
stateMachine.MoveNext();
}
finally
{
ecsw.Undo();
}
}

这个方法中调用了状态机的MoveNext方法,相当于启动了状态机。而状态机的第一个状态会运行await前面的代码。所以,GetBaiduHtmlAsync方法中await前面的代码的运行并不是运行其返回的task的结果。而是这里AsyncTaskMethodBuilder的Start方法调用了MoveNext方法的结果。并且是同步调用,因此是运行在GetBaiduHtmlAsync方法的调用线程里。由此可以看出,在Async&Await编程模型中,Task其实只是表达异步任务和承载任务状态和结果,而任务的运行其实通过状态机的状态改变来推动。至此解释了第3和第5个问题。

问题2        
至于第2个问题,根据上面反编译的代码,await关键字被编译器翻译为
1. 获取异步任务的awaiter
2. 基于awaiter调用
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<WebResponse>, Program.<GetBaiduHtmlAsync>d__2>(ref awaiter, ref this);
而这个调用是依赖根据async所生成的一整套代码(状态机,task builder...)所以await必须在async的环境中使用。

C# Async&Await的更多相关文章

  1. async & await 的前世今生(Updated)

    async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...

  2. [.NET] 利用 async & await 的异步编程

    利用 async & await 的异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/5922573.html  目录 异步编程的简介 异 ...

  3. [.NET] 怎样使用 async & await 一步步将同步代码转换为异步编程

    怎样使用 async & await 一步步将同步代码转换为异步编程 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6079707.html  ...

  4. [.NET] 利用 async & await 进行异步 IO 操作

    利用 async & await 进行异步 IO 操作 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6082673.html  序 上次,博主 ...

  5. [C#] 走进异步编程的世界 - 开始接触 async/await

    走进异步编程的世界 - 开始接触 async/await 序 这是学习异步编程的入门篇. 涉及 C# 5.0 引入的 async/await,但在控制台输出示例时经常会采用 C# 6.0 的 $&qu ...

  6. ASP.NET 中的 Async/Await 简介

    本文转载自MSDN 作者:Stephen Cleary 原文地址:https://msdn.microsoft.com/en-us/magazine/dn802603.aspx 大多数有关 async ...

  7. C# async/await 使用总结

    今天搞这两个关键字搞得有点晕,主要还是没有彻底理解其中的原理. 混淆了一个调用异步方法的概念: 在调用异步方法时,虽然方法返回一个 Task,但是其中的代码已经开始执行.该方法在调用时,即刻执行了一部 ...

  8. 【转】async & await 的前世今生

    async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...

  9. async & await 的前世今生

    async 和 await 出现在C# 5.0之后,给并行编程带来了不少的方便,特别是当在MVC中的Action也变成async之后,有点开始什么都是async的味道了.但是这也给我们编程埋下了一些隐 ...

  10. C# Async, Await and using statements

    Async, Await 是基于 .NEt 4.5架构的, 用于处理异步,防止死锁的方法的开始和结束, 提高程序的响应能力.比如: Application area           Support ...

随机推荐

  1. java 的异常处理

    一.异常的概念: java 中的异常通常指的是在运行期出现的错误,这样的错误也是比较难以调试的,解决问题的时候注意观察出现错误的名字和行号最重要 下面看这个例子: import java.io.*; ...

  2. 20145221 《Java程序设计》第一周学习总结

    20145221 <Java程序设计>第一周学习总结 教材学习内容总结 第一周内容已在假期完成,详见博客: Hello Java! 开源中国的代码托管 代码调试中的问题和解决过程 第一周内 ...

  3. 数据导入(一):Hive On HBase

    Hive集成HBase可以有效利用HBase数据库的存储特性,如行更新和列索引等.在集成的过程中注意维持HBase jar包的一致性.Hive与HBase的整合功能的实现是利用两者本身对外的API接口 ...

  4. java虚拟机类加载

    java虚拟机中类的加载 (JVM的大致结构图) 从发class文件到内存中的类,按先后顺序,需要经过加载,链接以及初始化三大步骤. java语言的类型可分为两大类:基本类型(primitive ty ...

  5. Python学习札记(三十二) 面向对象编程 Object Oriented Program 3

    参考:访问限制 NOTE 1.eg. #!/usr/bin/env python3 class Student(object): """docstring for Stu ...

  6. Codeforces Round #390 (Div. 2) A. Lesha and array splitting

    http://codeforces.com/contest/754/problem/A 题意: 给出一串序列,现在要把这串序列分成多个序列,使得每一个序列的sum都不为0. 思路: 先统计一下不为0的 ...

  7. 51Nod 1737 配对(树的重心)

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1737 题意: 思路: 树的重心. 树的重心就是其所以子树的最大的子树结点 ...

  8. 2018-2019-2 20165332《网络攻防技术》Exp5 MSF基础应用

    2018-2019-2 20165332<网络攻防技术>Exp5 MSF基础应用 1.基础问题回答 用自己的话解释什么是exploit,payload,encode. exploit:就是 ...

  9. 谈谈oracle里的join、left join、right join

    create table l as select 'left_1' as str,'1' as v from dual union allselect 'left_2' ,'2' as v from ...

  10. google搜索 site:pku.edu.cn inurl:aspx 即可查找所有动态网页 =====html(静态网页) asp(动态) jsp(动态) php(动态) cgi(网络程序) aspx(动态)

    shodan shodan和我们国内的钟馗之眼是一种搜索引擎,他们区别于百度等引擎,他们只爬设备,只爬联网设备. 网址为: https://www.shodan.io/ Shodan,也有人把他叫撒旦 ...