zoukankan      html  css  js  c++  java
  • 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. 内部源代码实现还是有点复杂的,暂时看得不是很懂,有空再看看。。。
  • 相关阅读:
    【技巧总结】公开漏洞学习
    【 Keepalived 】Nginx or Http 主-主模式
    【 Keepalived 】Nginx or Http 主-备模式
    【 转 】Keepalived工作原理
    【 总结 】crontab 使用脚本及直接获取HTTP状态码
    【 总结 】linux中test命令详解
    【 总结 】Tcp Keepalive 和 HTTP Keepalive 详解
    【 Linux 】I/O工作模型及Web服务器原理
    【 Ngnix 】配置路径转发至后端Apache多台虚拟主机
    【 Linux】脚本导入格式
  • 原文地址:https://www.cnblogs.com/yongfengnice/p/14891049.html
Copyright © 2011-2022 走看看