kotlin協程suspend關鍵字源碼解析


kotlin協程suspend關鍵字源碼解析(可能有點亂,順着分析順着寫點,將就看特別是看注釋說明,相信還是有點收獲的)

//1. 自己編寫的kotlin源代碼
private val mainScope = MainScope()
fun xSuspend(view: View) {
	mainScope.launch {
		//coroutine開始
 		Log.d("Suyf", "xSuspend: 111111111111111" + Thread.currentThread())
        val result = loginRepository.makeLoginRequest2()
        Log.d("Suyf", "xSuspend: 4444444444444444" + Thread.currentThread())
        if (TextUtils.isEmpty(result)) {
            showToast("Error:${result}")
        } else {
            showToast("Success:${result}")
        }
        //coroutine結束
    }
    Log.d("Suyf", "xSuspend: 555555555555555555555" + Thread.currentThread())
}
private fun showToast(text: String) {
    Toast.makeText(this@CoroutineActivity, text, Toast.LENGTH_SHORT).show()
}

//1. 編譯器最后生成的代碼
public final void xSuspend(View view) {
	Intrinsics.checkParameterIsNotNull(view, "view");
    Job unused = BuildersKt__Builders_commonKt.launch$default(this.mainScope, null, null, new CoroutineActivity$xSuspend$1(this, null), 3, null);
    Log.d("Suyf", "xSuspend: 555555555555555555555" + Thread.currentThread());
}
/* access modifiers changed from: private */
public final void showToast(String text) {
    Toast.makeText(this, text, 0).show();
}

//1. 協程恢復后要執行的回調方法invokeSuspend
public final class CoroutineActivity$xCoroutineScope$1 extends SuspendLambda{
    public final Object invokeSuspend(Object $result) {
        //這里代碼邏輯等價於上面[coroutine開始]到[coroutine結束]的代碼邏輯
    }
}

//1. 分析:編譯器會自動生成一個類CoroutineActivity$xSuspend$1,將上面的[coroutine開始]到[coroutine結束]的代碼也就是協程回調的代碼塊放進invokeSuspend方法,然后切換到子線程執行完了,再切換恢復到主線程,執行這個類的invokeSuspend方法。
//2. 自己編寫的kotlin源代碼,帶有suspend關鍵字。LoginRepository類的makeLoginRequest2方法如下:
suspend fun makeLoginRequest2(): String? {
    Log.d("Suyf", "xSuspend: 2222222222222222" + Thread.currentThread())
    return withContext(Dispatchers.IO) {
        try {
            Log.d("Suyf", "xSuspend: 3333333333333333" + Thread.currentThread())
            val url = URL(loginUrl)//網絡請求,耗時操作
            (url.openConnection() as? HttpURLConnection)?.run {
                requestMethod = "GET"
                setRequestProperty("Content-Type", "application/json; utf-8")
                setRequestProperty("Accept", "application/json")
                doOutput = true
                return@withContext readInputStream(inputStream)//讀取stream為字符串的普通方法
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return@withContext null
    }
}
//2. 編譯后生成的源代碼,自動生成LoginRepository$makeLoginRequest2$2類
// 自動添加continuation參數,實現回調功能。也就是說suspend掛起和恢復其實也是回調,
// 只是編譯器幫我們生成這些回調代碼了。。。
public final Object makeLoginRequest2(Continuation<? super String> continuation) {
	Log.d("Suyf", "xSuspend: 2222222222222222" + Thread.currentThread());
	return BuildersKt.withContext(Dispatchers.getIO(), new LoginRepository$makeLoginRequest2$2(this, null), continuation);
    }
//2. LoginRepository$makeLoginRequest2$2類繼承SuspendLambda 並實現了invoke和invokeSuspend方法
// 注意1編譯器會為每一個suspend掛起函數生成一個類繼承SuspendLambda,並實現invoke和invokeSuspend方法
// 注意:編譯時每遇到suspend關鍵字都是注意1這樣,那如果大量使用suspend關鍵字,無形中新增了很多類和方法。。
public final class LoginRepository$makeLoginRequest2$2 extends SuspendLambda{
    
    //供Dispatchers.getIO()線程調用
    @Override  // kotlin.jvm.functions.Function2
    public final Object invoke(CoroutineScope coroutineScope, Continuation<? super String> continuation) {
        return create(coroutineScope, continuation).invokeSuspend(Unit.INSTANCE);
    }

    @Override // kotlin.coroutines.jvm.internal.BaseContinuationImpl
    public final Object invokeSuspend(Object $result) {
        //具體的業務實現,可以是耗時操作
	}
}
  1. 那上面的Dispatchers.getIO()是個啥??就是一個IO子線程。。。就是我們平時說的子線程。。。
//LimitingDispatcher繼承Executor本身就是一個Executor
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
    val IO: CoroutineDispatcher = LimitingDispatcher(
        this,
        systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
        "Dispatchers.IO",
        TASK_PROBABLY_BLOCKING
    )
}
  1. BuildersKt.withContext()方法做了什么?是怎么切換線程的??
//第一步:在BuildersKt類里withContext方法
BuildersKt.withContext() -> BuildersKt__Builders_commonKt.withContext()
->最后是return suspendCoroutineUninterceptedOrReturn(),可以在該方法下斷點調試。

//第二步:創建DispatchedCoroutine
val coroutine = DispatchedCoroutine(newContext, uCont)
coroutine.initParentJob()
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()

//第三步:其實最關鍵是startCoroutineCancellable方法,里面進行了線程切換,
//通通過狀態機機制block與loop,最后切換回調用線程。。。
fun resumeCancellableWith(result:Result<T>,onCancellation:((cause: Throwable)->Unit)?){
        val state = result.toState(onCancellation)
        if (dispatcher.isDispatchNeeded(context)) {//是否需要轉發
            _state = state
            resumeMode = MODE_CANCELLABLE
            dispatcher.dispatch(context, this)//轉發,通過handler切換線程
        } else {
            //...
        }
    }
}

//第四步:通過handler切換線程
class HandlerContext:HandlerDispatcher(){
    //block即DispatchedContinuation[Dispatchers.Main, Continuation at xxx.invokeSuspend()]
    //可見這個block就是主線程上的一個Continuation,也就是一個回調
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        handler.post(block)
    }
}

//第五步:執行resume恢復,最后執行編譯器為我們自動生成的invokeSuspend方法,回到開始點的地方即恢復。
class DispatchedTask<in T>() : SchedulerTask() {
    public final override fun run() {
        //...
        continuation.resume(getSuccessfulResult(state))
    }
}

總結1:

  1. Job類:啟動協程的兩種方式即launch和async。launch是啟動直接執行,async是啟動后需要await觸發執行。啟動協程返回的結果就是一個Job,可以通過job取消協程等等操作。
  2. withContext方法:線程切換,注意是切換執行協程的線程,也就是說指定在哪個線程執行協程代碼塊。
  3. CoroutineDispatcher類:協程分發器,將協程分發到哪個線程去執行,配合withContext方法使用。
  4. CoroutineContext類:協程上下文,用於暫停或者恢復協程時,保存和恢復現場等場景。
  5. CoroutineScheduler類:協程執行器,內部實現就是使用的我們熟悉的線程池Executor。
  6. CoroutineScope類:跟蹤使用 launchasync 創建的所有協程。可以調用 scope.cancel() 以取消正在進行的同一Scope的工作Job(即正在運行的協程),簡單來說就是管理一組Job的。

總結2:

  1. 協程執行耗時操作時也是依靠子線程去完成的,從子線程切換回UI線程也是依靠我們平時接觸的Android Handler類完成的。
  2. 我們寫的協程代碼看起來是同步形式的,其實也是異步回調的,只是編譯器幫我們自動生成了回調代碼而已。編譯器將suspend形式的同步代碼,生成帶有Continuation回調形式的代碼。

總結3:

  1. 雖然說協程內部也是協助線程池和Android Handler完成子線程和UI線程的切換。但是為了充分利用線程資源和減少線程切換,內部也維護着自己的調用棧,所以出現CoroutineContext、CoroutineScope等新的概念。
  2. 內部源代碼實現還是有點復雜的,暫時看得不是很懂,有空再看看。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM