kotlin的suspend對比csharp的async&await
協程的出現大大降低了非同步編程的複雜度,可以讓我們像寫同步程式碼一樣去寫非同步程式碼,如果沒有它,那麼很多非同步的程式碼都是需要靠回調函數來一層層嵌套,這個在我之前的一篇有介紹 rxjava回調地獄-kotlin協程來幫忙
本篇文章主要介紹
-
kotlin的suspend函數在編譯生成了怎樣的程式碼 -
csharp的async&await在編譯生成了怎麼樣的程式碼 -
這兩者相比較,引發怎樣的思考
kotlin的suspend函數demo
這裡針對kotlin的語法以及協程的具體用法細節不過多介紹,就當你已了解
稍微注意下runBlocking函數比較特別,
如下圖:它接受了一個suspend的block函數
所以我上面的demo這裡面有其實有三個suspend函數!
在idea我們可以把這個kotlin程式碼反編譯成java程式碼
這個反編譯後的java程式碼 有很多報錯是無法直接copy出來運行的(這就沒有csharp做的好,csharp反編譯出來的程式碼至少不會報紅),
看程式碼的確是一個狀態機控制函數和一個匿名類,還原成正常的java程式碼如下:
比如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);
}
@Nullable
public Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return test1(this);
}
}
其他的函數也類似,完整的程式碼請查看:
//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);
}
}
完整程式碼請查看 //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")
// 或者手動取消當前coroutineScope
this.cancel()
}