一、難以被接受的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#代碼:
其他的暫時不管,先嘗試把上面這段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
新聞熱點
疑難解答