1.同步与异步

假设存在

IO事件A:请求网络资源 (完成耗时5s)

IO事件B:查询数据库 (完成耗时5s)

情况一:线程1工人在发起A请求后,一直阻塞等待,在A响应返回结果后再接着处理事件B,那总共需要耗时>10s.

情况二:线程1工人在发起A请求后,马上返回发起B请求然后返回,5s后事件A响应返回,接着事件B响应返回,那总共需要耗时<10s.

情况一就是同步的概念,而情况二就是异步的概念。细节会有所不同,但大致上可以这样理解。然而并不是所有情况适用异步,下面将会解释。

2.异步运行的顺序

   c#中的异步关键词是async与await,常常结合Task使用,如下面实例,看看它执行的情况

 static async Task Main(string[] args)
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:MainStart"); //标记
await SayHi();
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:MainEnd"); //标记4
} static async Task SayHi()
{
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:SayHiStart"); //标记
await Task.Delay();
Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}:SayHiEnd"); //标记3
}

结果:

1:MainStart
1:SayHiStart
5:SayHiEnd
5:MainEnd

c#7.1后的版本都支持异步main方法,程序执行的状况

线程1->标记1,

线程1->标记2,

线程5->标记3

线程5->标记4

执行顺序如预期,而需要关注的是线程在执行期间的切换,在线程1执行完标记2后就已经返回,接着由线程5接管了后面代码逻辑的执行,那到底为什么会发生这样的情况?

答案是:编译器会自动地替我们完成了大量了不起的工作,下面接着来看看。

3.生成骨架与状态机

编译器在遇到await关键字会自动构建骨架与生成状态机,按照以上例子来看看编译器做的工作有那些。

[DebuggerStepThrough]
private static void <Main>(string[] args)
{
Main(args).GetAwaiter().GetResult();
} [AsyncStateMachine((Type) typeof(<Main>d__0)), DebuggerStepThrough]
private static Task Main(string[] args)
{
<Main>d__0 stateMachine = new <Main>d__0 {
args = args,
<>t__builder = AsyncTaskMethodBuilder.Create(),
<>1__state = -
};
stateMachine.<>t__builder.Start<<Main>d__0>(ref stateMachine);
return stateMachine.<>t__builder.get_Task();
} [AsyncStateMachine((Type) typeof(<SayHi>d__1)), DebuggerStepThrough]
private static Task SayHi()
{
<SayHi>d__1 stateMachine = new <SayHi>d__1 {
<>t__builder = AsyncTaskMethodBuilder.Create(), //如果返回的是void builder为AsyncVoidMethodBuilder
<>1__state = -1 //状态初始化为-1
};
stateMachine.<>t__builder.Start<<SayHi>d__1>(ref stateMachine); //开始执行 传入状态机的引用
return stateMachine.<>t__builder.get_Task(); //返回结果
}

1.编译器会自动生成void mian程序入口方法,它会调用async Task main方法。(所以说c#7.1支持异步main方法,其实只是编译器做了一点小工作)

2.main方法里的输出内容与调用SayHi方法代码消失了,取而代之的是编译器生成了骨架方法,初始化 <Main>d__0 状态机,把状态机的状态字段<>1__state

初始化为-1,builder为AsyncTaskMethodBuilder实例,接着调用builder的Start方法。

3.SayHi方法同2

接着看看AsyncTaskMethodBuilder的Start方法

[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine: IAsyncStateMachine
{
if (((TStateMachine) stateMachine) == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
Thread currentThread = Thread.CurrentThread;
Thread thread2 = currentThread;
ExecutionContext context2 = currentThread._executionContext;
SynchronizationContext context3 = currentThread._synchronizationContext;
try
{
stateMachine.MoveNext(); //调用了状态机的MoveNext方法
}
finally
{
SynchronizationContext context4 = context3;
Thread thread3 = thread2;
if (!ReferenceEquals(context4, thread3._synchronizationContext))
{
thread3._synchronizationContext = context4;
}
ExecutionContext contextToRestore = context2;
ExecutionContext currentContext = thread3._executionContext;
if (!ReferenceEquals(contextToRestore, currentContext))
{
ExecutionContext.RestoreChangedContextToThread(thread3, contextToRestore, currentContext);
}
}
}

Start方法调用了状态机的MoveNext方法,是不是很熟悉?接下来看看状态机长什么样子。

[CompilerGenerated]
private sealed class <Main>d__0 : IAsyncStateMachine
{
// Fields
public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
public string[] args;
private TaskAwaiter <>u__1; // Methods
private void MoveNext()
{
int num = this.<>1__state;
try
{
TaskAwaiter awaiter;
if (num == )
{
awaiter = this.<>u__1;
this.<>u__1 = new TaskAwaiter();
this.<>1__state = num = -;
goto TR_0004;
}
else //1: <>1_state初始值为-1,所以先进到该分支,由线程1执行
{
Console.WriteLine($"{(int) Thread.get_CurrentThread().ManagedThreadId}:MainStart"); //标记1 //线程1执行 所以输出 1:MainStart
awaiter = Program.SayHi().GetAwaiter(); //重点:获取Taskd GetAwaiter方法返回TaskAwaiter
if (awaiter.IsCompleted) //重点:判断任务是否已经完成
{
goto TR_0004; //SayHi方法是延时任务,所以正常情况下不会跳进这里
}
else
{
this.<>1__state = num = ; //赋值状态0
this.<>u__1 = awaiter;
Program.<Main>d__0 stateMachine = this;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<Main>d__0>(ref awaiter, ref stateMachine); //重点:把TaskAwaiter与该状态机,线程1执行到这返回
}
}
                 return;
TR_0004:
awaiter.GetResult(); //重点:获取结果 由线程1执行或延时任务不定线程执行
Console.WriteLine($"{(int) Thread.get_CurrentThread().ManagedThreadId}:MainEnd"); //标记4 所以输出 5:MainEnd
                 this.<>1__state = -; this.<>t__builder.SetResult();//设置结果 
}
catch (Exception exception)
{
this.<>1__state = -;
this.<>t__builder.SetException(exception); //设置异常
}
}
[DebuggerHidden] private void SetStateMachine(IAsyncStateMachine stateMachine) { } }

上面我圈了重点的是关于Task类型能实现async await的关键操作,

1.线程1执行调用Task实例的GetAwaiter方法返回TaskAwaiter实例。

2.判断TaskAwaiter实例的IsCompleted属性是否完成,如果已完成,跳转到TR_0004,否则执行到AwaitUnsafeOnCompleted方法,线程1结束返回。

我们继续来看看AwaitUnsafeOnCompleted方法,没反编译出来,所以我们来看看与它类似的AwaitOnCompleted方法( AwaitUnsafeOnCompleted实际上会调用UnsafeOnCompleted方法)

public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter: INotifyCompletion where TStateMachine: IAsyncStateMachine
{
try
{
awaiter.OnCompleted(this.GetStateMachineBox<TStateMachine>(ref stateMachine).MoveNextAction);
}
catch (Exception exception1)
{
Task.ThrowAsync(exception1, null);
}
}

看到这里是不是豁然开朗了

1.注册TaskAwaiter实例完成任务的回调方法,等任务完成后将会调用状态机的MoveNext方法,由上篇文章Task的启动方式知道后面的操作将会交由线程池的线程处理。所以标记3跟标记4将会在空闲的线程上执行。

2.<>1__state为0,跳到TR_0004执行,调用TaskAwaiter实例的GetResult()方法,执行await后面的代码,返回结果。

SayHi方法同上。

结论

编译器遇到await后会自动构建骨架与状态机,把await后面的代码挪到任务完成的后面继续执行。主线程第一次调用MoveNext方法时,如果任务已经完成会直接执行后面的操作,否则直接返回,不阻塞主线程的运行。后面的流程

将交由线程池来调度完成。

回到文章开头的问题,什么情况下不适用异步?

可以看出来,使用异步编译器会生成大量额外的操作,而不耗时或者CPU密集型工作使用异步就是添堵。

思考

是不是只有Task才能用async与await?

下一篇我将来探讨一下这个问题,感兴趣的小伙伴可以关注留意后续更新

有说得不对的地方欢迎大神指正,欢迎讨论,共同进步

Async,Await 深入源码解析的更多相关文章

  1. 异步任务spring @Async注解源码解析

    1.引子 开启异步任务使用方法: 1).方法上加@Async注解 2).启动类或者配置类上@EnableAsync 2.源码解析 虽然spring5已经出来了,但是我们还是使用的spring4,本文就 ...

  2. jQuery2.x源码解析(回调篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 通过艾伦的博客,我们能看出,jQuery的pro ...

  3. Koa2 源码解析(1)

    Koa2 源码解析 其实本来不想写这个系列文章的,因为Koa本身很精简,一共就4个文件,千十来行代码. 但是因为想写 egg[1] 的源码解析,而egg是基于Koa2的,所以就先写个Koa2的吧,用作 ...

  4. Ocelot简易教程(七)之配置文件数据库存储插件源码解析

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html 上篇文章给大家分享了如何集成我写的一个Ocelot扩展插件把Ocelot的配置存储 ...

  5. .Net Core缓存组件(Redis)源码解析

    上一篇文章已经介绍了MemoryCache,MemoryCache存储的数据类型是Object,也说了Redis支持五中数据类型的存储,但是微软的Redis缓存组件只实现了Hash类型的存储.在分析源 ...

  6. redux的源码解析

    一. redux出现的动机 1. Javascript 需要管理比任何时候都要多的state2. state 在什么时候,由于什么原因,如何变化已然不受控制.3. 来自前端开发领域的新需求4. 我们总 ...

  7. 简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析

    简单理解 OAuth 2.0 及资料收集,IdentityServer4 部分源码解析 虽然经常用 OAuth 2.0,但是原理却不曾了解,印象里觉得很简单,请求跳来跳去,今天看完相关介绍,就来捋一捋 ...

  8. Redux系列x:源码解析

    写在前面 redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分还是 这里假设读者对redux有一定了解,就不科普redux的概念和API啥的啦,这部分建议直接看官方文档. ...

  9. DotNetOpenAuth Part 1 : Authorization 验证服务实现及关键源码解析

    DotNetOpenAuth 是 .Net 环境下OAuth 开源实现框架.基于此,可以方便的实现 OAuth 验证(Authorization)服务.资源(Resource)服务.针对 DotNet ...

随机推荐

  1. mysql的Ft_hints: no_ranking

    是不是发现找遍全网也没有找到相关资料? 巧了,我也是,所以我这里来进行一次大胆分析(基本靠猜) 在使用mysql的fulltext索引(全文索引)时,使用explain则会在extra中出现这句提示: ...

  2. [POJ2533]Longest Ordered Subsequence<dp>

    题目链接:http://poj.org/problem?id=2533 描述: A numeric sequence of ai is ordered if a1 < a2 < ... & ...

  3. 宝塔phpmyadmin可能问题及解决方法

    1. 端口问题检查宝塔phpmyadmin的默认端口888是否放行,和在服务器的安全组规则有没有添加888端口 2.phpmyadmin的php版本问题 在phpmyadmin的设置里的版本选择php ...

  4. EF 太重,MyBatis 太轻,ORM 框架到底怎么选 ?

    以 EF 为代表的基于 Linq 的 ORM 框架总是 很重. 他们的功能早已超出了一个 ORM 的范畴, ORM 是 Object Relational Mapping ,从名字上看,其初衷是将 数 ...

  5. stm32:简单按键输入实现

    开发环境keil4,芯片STM32F103C8T6 1.main.c //串口实验 #include "sys.h" #include "delay.h" #i ...

  6. node.js模拟学校教务处登录

    临近毕业,在做毕设,我的毕设中有一个功能是模拟我学校的教务处登录以获得cookie,本来以为是挺简单的一个功能,但却花了我两天的时间.(我学校教务处用的是湖南强智科技开发的) 在网上搜了大量的模拟登录 ...

  7. Java第十七天,Set接口

    Set接口 1.特点 (1)不包含重复元素. (2)没有索引. (3)继承自Collection接口,所以Collection接口中的所有方法都适用于Set接口. 2.解析 (1)为什么不能包含重复元 ...

  8. webstorm 永久激活方法

    打开终端,执行: cd /etc/ sudo vim hosts 在最后一行加上: 0.0.0.0 account.jetbrains.com 打开webstorm,选择Activation Code ...

  9. matplotlib BlendedAffine2D 和 CompositeAffine2D

    2020-04-11 10:00:01 --Edit by yangrayBlendedAffine2D 继承于Affine2DBase,支持x和y方向使用不同的仿射变换策略.(自译:混合仿射变换)C ...

  10. 14-jmeter分布式环境

    1.分布式概念: jmeter做性能时,会消耗本地机器资源 本机无法没有限制的创建运行线程(一般500线程就差不多会报错) 一般这时候会用到分布式的环境 2.环境: 前提条件:环境一致(有时候可以直接 ...