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. 内部源代码实现还是有点复杂的,暂时看得不是很懂,有空再看看。。。
posted @ 2021-06-16 21:01  yongfengnice  阅读(768)  评论(0编辑  收藏  举报