協程的出現大大降低了異步編程的復雜度,可以讓我們像寫同步代碼一樣去寫異步代碼,如果沒有它,那么很多異步的代碼都是需要靠回調函數來一層層嵌套,這個在我之前的一篇有介紹 rxjava回調地獄-kotlin協程來幫忙
本篇文章主要介紹
kotlin的suspend函數在編譯生成了怎樣的代碼
csharp的async&await在編譯生成了怎么樣的代碼
這兩者相比較,引發怎樣的思考
kotlin的suspend函數demo

這里針對kotlin的語法以及協程的具體用法細節不過多介紹,就當你已了解
稍微注意下runBlocking函數比較特別,
如下圖:它接受了一個suspend的block函數

所以我上面的demo這里面有其實有三個suspend函數!
在idea我們可以把這個kotlin代碼反編譯成java代碼

這個反編譯后的java代碼 有很多報錯是無法直接copy出來運行的(這就沒有csharp做的好,csharp反編譯出來的代碼至少不會報紅),

看代碼的確是一個狀態機控制函數和一個匿名類,還原成正常可直接運行的java代碼如下:
測試用的coroutine版本為:
kotlinx-coroutines-jdk8:1.6.4

比如test1函數
public?static?Object?test1(Continuation?continuation)?{CoroutineTest1?continuationTest1;label20:{if?(continuation?instanceof?CoroutineTest1)?{continuationTest1?=?(CoroutineTest1)?continuation;int?i?=?continuationTest1.label?&?Integer.MIN_VALUE;if?(i?!=?0)?{continuationTest1.label?-=?Integer.MIN_VALUE;}break?label20;}continuationTest1?=?new?CoroutineTest1(continuation);}Object?result?=?(continuationTest1).result;Object?var4?=?IntrinsicsKt.getCOROUTINE_SUSPENDED();String?var1;switch?((continuationTest1).label)?{case?0:ResultKt.throwOnFailure(result);var1?=?"test1-start";System.out.println(var1);(continuationTest1).label?=?1;if?(test2(continuationTest1)?==?var4)?{return?var4;}break;case?1:ResultKt.throwOnFailure(result);break;default:throw?new?IllegalStateException("call?to?'resume'?before?'invoke'?with?coroutine");}var1?=?"test1-end";System.out.println(var1);return?Unit.INSTANCE;
}final?static?class?CoroutineTest1?extends?ContinuationImpl?{Object?result;int?label;public?CoroutineTest1(@Nullable?Continuation<Object>?completion)?{super(completion);}@Nullablepublic?Object?invokeSuspend(@NotNull?Object?$result)?{this.result?=?$result;this.label?|=?Integer.MIN_VALUE;return?test1(this);}
}
其他的函數也類似,完整的代碼請查看:
https://gist.github.com/yuzd/cf67048777f0eb8fc1b3757f5bf9e8f3
整個運行流程如下:
kotlin協程的掛起點是怎么控制的,異步操作執行完后它知道從哪里恢復?
不難看出來suspend函數其實在編譯后是變成了狀態機,將我們順序執行的代碼,轉換成了回調的形式父suspend函數里面調用子suspend函數,其實是把自己傳給了子suspend狀態機,如果子函數掛起了,等子函數恢復后直接調用父函數(因為通過狀態機的label來控制走不同邏輯,去恢復當時的調用堆棧)
這就是協程的掛起與恢復機制了
csharp的async&await
demo
static?async?Task?Main(string[]?args)
{await?test1();??????Console.WriteLine("Let's?Go!");
}async?Task?test1(){Console.WriteLine("test1-start");await?test2();Console.WriteLine("test1-end");}async?Task?test2()
{Console.WriteLine("test2-start");await?Task.Delay(1000);Console.WriteLine("test2-end");}
我們反編譯查看下編譯器生成了怎樣的狀態機

看反編譯的代碼比較吃力,我還原成了正常代碼,
static?Task?CreateMainAsyncStateMachine()
{MainAsyncStateMachine?stateMachine?=?new?MainAsyncStateMachine{_builder?=?AsyncTaskMethodBuilder.Create(),_state?=?-1};stateMachine._builder.Start(ref?stateMachine);return?stateMachine._builder.Task;
}struct?MainAsyncStateMachine?:?IAsyncStateMachine
{public?int?_state;public?AsyncTaskMethodBuilder?_builder;public?TaskAwaiter?_waiter;public?void?MoveNext(){int?num1?=?this._state;try{TaskAwaiter?awaiter;int?num2;if?(num1?!=?0){awaiter?=?UserQuery.CreateTest1AsyncStateMachine().GetAwaiter();if?(!awaiter.IsCompleted){Console.WriteLine("MainAsyncStateMachine######Test1AsyncStateMachine?IsCompleted:false,?注冊自己到Test1Async運行結束時運行");this._state?=?num2?=?0;this._waiter?=?awaiter;this._builder.AwaitUnsafeOnCompleted(ref?awaiter,?ref?this);return;}}else{Console.WriteLine("MainAsyncStateMachine######Test1AsyncStateMachine?IsCompleted:true");awaiter?=?this._waiter;this._waiter?=?new?TaskAwaiter();this._state?=?num2?=?-1;}awaiter.GetResult();Console.WriteLine("MainAsyncStateMachine######Let's?Go!");}catch?(Exception?e){this._state?=?-2;this._builder.SetException(e);return;}this._state?=?-2;this._builder.SetResult();}public?void?SetStateMachine(IAsyncStateMachine?stateMachine){this._builder.SetStateMachine(stateMachine);}
}
完整代碼請查看https://github.com/yuzd/asyncawait_study
可以看出來,和kotlin其實原理差不多,都是生成一個函數加一個狀態機
區別是csharp的函數就是創建一個狀態機且啟動它
//?當狀態機啟動時會觸發?狀態機的MoveNext方法的調用
stateMachine._builder.Start(ref?stateMachine);

整體的執行流程如下

ps:最右邊的是展示如果有多個await 那么就會對應這個狀態機的多個狀態
這兩者相比較,引發怎樣的思考
通過查看kotlin和csharp的實現方式,我發現kotlin的生成的狀態機(ContinuationImpl的實現)都是有繼承關系的, 比如demo中的test2繼承了test1,test繼承了main(通過構造函數傳遞的)
然而csharp中沒有這樣的關系
這也帶來了兩者最大的區別,kotlin的協程綁定了scope的概念,一旦scope被取消,那么scope綁定的所有的協程也都被取消。
這點好像在csharp中沒有(如果理解有誤歡迎指正)
這在實際應用中是怎么個區別呢,舉個例子
async?void?testAsyncA(){testAsyncB();//?我想取消,或者下面運行出異常了?我也無法取消testAsyncB這個任務}async?void?testAsyncB(){//?do?long?task
}
在kotlin是可以的

suspend?fun?test2()?=?coroutineScope?{println("test2-start")async?{delay(100000);}delay(1000)println("test2-end")//?或者手動取消當前coroutineScopethis.cancel()
}
?我是正東,關注高效率編程~