首页 > 学院 > 开发设计 > 正文

[C#]剖析异步编程语法糖: async和await

2019-11-17 02:18:32
字体:
来源:转载
供稿:网友

[C#]剖析异步编程语法糖: async和await

一、难以被接受的async

自从C#5.0,语法糖大家庭又加入了两位新成员: async和await。然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模式对于习惯了传统模式的人来说实在是有些难以接受,不难想象有多少人仍然在使用手工回调委托的方式来进行异步编程。C#中的语法糖非常多,从自动属性到lock、using,感觉都很好理解很容易就接受了,为什么偏偏async和await就这么让人又爱又恨呢?我想,不是因为它不好用(相反,理解了它们之后是非常实用又易用的),而是因为它来得太迟了!传统的异步编程在各种语言各种平台前端后端差不多都是同一种模式,给异步请求传递一个回调函数,回调函数中再对响应进行处理,发起异步请求的地方对于返回值是一无所知的。我们早就习惯了这样的模式,即使这种模式十分蹩脚。而async和await则打破了请求发起与响应接收之间的壁垒,让整个处理的逻辑不再跳过来跳过去,成为了完全的线性流程!线性才是人脑最容易理解的模式!广告时间:[C#]async和await刨根问底这篇随笔把本文未解决的问题都搞定了,并且对async和await的总体面貌做了最终总结,对调查过程没有兴趣希望直接看结果的可以直接戳进去~

二、理解async,谁被异步了

如果对于java有一定认识,看到async的使用方法应该会觉得有些眼熟吧?

//Javasynchronized void sampleMethod() { }
// C#async void SampleMethod() { }

说到这里我想对MS表示万分的感谢,幸好MS的设计师采用的简写而不是全拼,不然在没有IDE的时候(比如写上面这两个示例的时候)我不知道得检查多少次有没有拼错同步或者异步的单词。。。Java中的synchronized关键字用于标识一个同步块,类似C#的lock,但是synchronized可以用于修饰整个方法块。而C#中async的作用就是正好相反的了,它是用于标识一个异步方法。同步块很好理解,多个线程不能同时进入这一区块,就是同步块。而异步块这个新东西就得重新理解一番了。先看看async到底被编译成了什么吧:

 1 .method PRivate hidebysig  2     instance void SampleMethod () cil managed  3 { 4     .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 5         01 00 1f 54 65 73 74 2e 50 72 6f 67 72 61 6d 2b 6         3c 53 61 6d 70 6c 65 4d 65 74 68 6f 64 3e 64 5f 7         5f 30 00 00 8     ) 9     .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (10         01 00 00 0011     )12     // Method begins at RVA 0x20b013     // Code size 46 (0x2e)14     .maxstack 215     .locals init (16         [0] valuetype Test.Program/'<SampleMethod>d__0',17         [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder18     )19 20     IL_0000: ldloca.s 021     IL_0002: ldarg.022     IL_0003: stfld class Test.Program Test.Program/'<SampleMethod>d__0'::'<>4__this'23     IL_0008: ldloca.s 024     IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create()25     IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder Test.Program/'<SampleMethod>d__0'::'<>t__builder'26     IL_0014: ldloca.s 027     IL_0016: ldc.i4.m128     IL_0017: stfld int32 Test.Program/'<SampleMethod>d__0'::'<>1__state'29     IL_001c: ldloca.s 030     IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder Test.Program/'<SampleMethod>d__0'::'<>t__builder'31     IL_0023: stloc.132     IL_0024: ldloca.s 133     IL_0026: ldloca.s 034     IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<valuetype Test.Program/'<SampleMethod>d__0'>(!!0&)35     IL_002d: ret36 } // end of method Program::SampleMethod

不管你们吓没吓到,反正我第一次看到是吓了一大跳。。。之前的空方法SampleMethod被编译成了这么一大段玩意。另外还生成了一个名叫'<SampleMethod>d__0'的内部结构体,整个Program类的结构就像这样:其他的暂时不管,先尝试把上面这段IL还原为C#代码:

 1 void SampleMethod() 2 { 3     '<SampleMethod>d__0' local0; 4     AsyncVoidMethodBuilder local1; 5      6     local0.'<>4_this' = this; 7     local0.'<>t__builder' = AsyncVoidMethodBuilder.Create(); 8     local0.'<>1_state' = -1; 9     10     local1 = local0.'<>t__builder';11     local1.Start(ref local0);12 }

跟进看Start方法:

1 // System.Runtime.CompilerServices.AsyncVoidMethodBuilder2 [__DynamicallyInvokable, DebuggerStepThrough]3 public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine4 {5     this.m_coreState.Start<TStateMachine>(ref stateMachine);6 }

继续跟进:

 1 // System.Runtime.CompilerServices.AsyncMethodBuilderCore 2 [DebuggerStepThrough, SecuritySafeCritical] 3 internal void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 4 { 5     if (stateMachine == null) 6     { 7         throw new ArgumentNullException("stateMachine"); 8     } 9     Thread currentThread = Thread.CurrentThread;10     ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);11     RuntimeHelpers.PrepareConstrainedRegions();12     try13     {14         ExecutionContext.EstablishCopyOnWriteScope(currentThread, false, ref executionContextSwitcher);15         stateMachine.MoveNext();16     }17     finally18     {19         executionContextSwitcher.Undo(currentThread);20     }21 }

注意到上面黄底色的stateMachine就是自动生成的内部结构体'<SampleMethod>d__0',再看看自动生成的MoveNext方法,IL就省了吧,直接上C#代码:

 1 void MoveNext() 2 { 3     bool local0; 4     Exception local1; 5      6     try 7     { 8         local0 = true; 9     }10     catch (Exception e)11     {12         local1 = e;13         this.'<>1__state' = -2;14         this.'<>t__builder'.SetException(local1);15         return;16     }17 18     this.'<>1__state' = -2;19     this.'<>t__builder'.SetResult()20 }

因为示例是返回void的空方法,所以啥也看不出来,如果在方法里头稍微加一点东西,比如这样:

async void SampleMethod(){    Thread.Sleep(1000);    Console.WriteLine("HERE");}

然后再看看SampleMethod的IL:

 1 .method private hidebysig  2     instance void SampleMethod () cil managed  3 { 4     .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 5         01 00 1f 54 65 73 74 2e 50 72 6f 67 72 61 6d 2b 6         3c 53 61 6d 70 6c 65 4d 65 74 68 6f 64 3e 64 5f 7         5f 30 00 00 8     ) 9     .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (10         01 00 00 0011     )12     // Method begins at RVA 0x20bc13     // Code size 46 (0x2e)14     .maxstack 215     .locals init (16         [0] valuetype Test.Program/'<SampleMethod>d__0',17         [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder18     )19 20     IL_0000: ldloca.s 021     IL_0002: ldarg.022     IL_0003: stfld class Test.Program Test.Program/'<SampleMethod>d__0'::'<>4__this'23     IL_0008: ldloca.s 024     IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Create()25     IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder Test.Program/'<SampleMethod>d__0'::'<>t__builder'26     IL_0014: ldloca.s 027     IL_0016: ldc.i4.m128     IL_0017: stfld int32 Test.Program/'<SampleMethod>d__0'::'<>1__state'29     IL_001c: ldloca.s 030     IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder Test.Program/'<SampleMethod>d__0'::'<>t__builder'31     IL_0023: stloc.132     IL_0024: ldloca.s 133     IL_0026: ldloca.s 034     IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncVoidMethodBuilder::Start<valuetype Test.Program/'<SampleMethod>d__0'>(!!0&)35     IL_002d: ret36 } // end of method Program::SampleMethod

看出来什么变化了吗?????看不出来就对了,因为啥都没变。那追加的代码跑哪去了?!在这呢:

 1 void MoveNext() 2 { 3     bool local0; 4     Exception local1; 5      6     try 7     { 8         local0 = true; 9         Thread.Sleep(1000);10         Console.WriteLine("HERE");11     }12     catch (Exception e)13     {14         local1 = e;15         this.'<>1__state' = -2;16         this.'<>t__builder'.SetException(local1);17         return;18     }19 20     this.'<>1__state' = -2;21     this.'<>t__builder'.SetResult()22 }

至今为止都没看到异步在哪发生,因为事实上一直到现在确实都是同步过程。Main方法里这么写:

static void Main(string[] args){    new Program().SampleMethod();    Console.WriteLine("THERE");    Console.Read();}

运行结果是这样的:

HERETHERE

"THERE"被"HERE"阻塞了,并没有异步先行。虽然到此为止还没看到异步发生,但是我们可以得出一个结论:async不会导致异步到底怎么才能异步?还是得有多个线程才能异步嘛,是时候引入Task了:

async void SampleMethod(){    Task.Run(() =>    {        T
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表