Kotlin 协程二三事:挂起原理

协程的本质还是线程

需要知道的是,因为Java虚拟机不支持协程,所以Kotlin的协程跑在Java还是通过线程来实现的。
通过kotlin编译成的class文件,我们可以看到,协程本质还是通过不同的线程池来实现不同的任务。我们通过Dispatchers类,可以看到协程中出创建的线程池。

public actual object Dispatchers {
    @JvmStatic
    public actual val Default: CoroutineDispatcher = createDefaultDispatcher()

    @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

    @JvmStatic
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined

    @JvmStatic
    public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

跟随源码,我们可以看到MainCoroutineDispatcher的实现

internal class AndroidDispatcherFactory : MainDispatcherFactory {

    override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
        HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")
}

由以上代码可以得出,Dispatcher.Main实际上就是一个主线程的Handler,把任务切换到主线程是去执行
再看一下Default的实现

private var coroutineScheduler = createScheduler()

private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)

internal class CoroutineScheduler(
    private val corePoolSize: Int,
    private val maxPoolSize: Int,
    private val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
    private val schedulerName: String = DEFAULT_SCHEDULER_NAME
) : Executor,Closeable

可以看到Dispatchers.default本质上是一个自定义的线程池。根据应用场景的不同,和Dispatcher.IO适用于不同的工作

状态机

协程通过状态机实现协程的挂起与恢复,以下是一段协程代码

    fun onCreate() {
        GlobalScope.launch {
            val result = getContent()
            Log.e("test", "onCreate: result = $result")
        }
    }

    suspend fun getContent(): String {
        return withContext(Dispatchers.Default) {
            Thread.sleep(3000)
            "xxx"
        }
    }

我们通过tools–>kotlin–>show kotlin bytecode,然后Decompile,可以看到生成的java代码

BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
		// 状态机状态的值
         int label;

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
         	// 挂起状态
            Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            Object var10000;
            switch(this.label) {
            case 0:
            	// 首次进来,默认为0
               ResultKt.throwOnFailure($result);
               GobalTest var4 = GobalTest.this;
               // 此时更新状态
               this.label = 1;
               // 执行挂起函数
               var10000 = var4.getContent(this);
               // 如果挂起,直接返回挂起值
               if (var10000 == var3) {
                  return var3;
               }
               break;
            case 1:
            	// 第二次进来时,走这,退出循环
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

			// 第二次执行打印语句
            String result = (String)var10000;
            Log.e("test", "onCreate: result = " + result);
            return Unit.INSTANCE;
         }

         @NotNull
         public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function2 var3 = new <anonymous constructor>(completion);
            return var3;
         }

         public final Object invoke(Object var1, Object var2) {
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
         }
      }), 3, (Object)null);

以上是onCreate部分,我们可以看到,成员变量label代表状态机的状态,当函数运行到挂起点的时候,label的值会发生变化,如果没有发生挂起,则函数继续执行,如果发生了挂起,则函数就直接return,直到协程恢复,再次调到invokeSuspend方法,此时lebal已经发生了变化,所以直接执行case 1的逻辑。

我们再看下getContent()方法

   @Nullable
   public final Object getContent(@NotNull Continuation $completion) {
      return BuildersKt.withContext((CoroutineContext)Dispatchers.getDefault(), (Function2)(new Function2((Continuation)null) {
         int label;

         @Nullable
         public final Object invokeSuspend(@NotNull Object var1) {
            Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(this.label) {
            case 0:
               ResultKt.throwOnFailure(var1);
               Thread.sleep(3000L);
               return "xxx";
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
         }

         @NotNull
         public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function2 var3 = new <anonymous constructor>(completion);
            return var3;
         }

         public final Object invoke(Object var1, Object var2) {
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
         }
      }), $completion);
   }

点进withContext方法,我们可以看到他的返回正是COROUTINE_SUSPENDED

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
   	...
    coroutine.getResult()
}

    fun getResult(): Any? {
        if (trySuspend()) return COROUTINE_SUSPENDED
        // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
        val state = this.state.unboxState()
        if (state is CompletedExceptionally) throw state.cause
        @Suppress("UNCHECKED_CAST")
        return state as T
    }

那么既然挂起了,何时恢复呢,我们看到BaseContinuationImpl这个类,看下resumewith的方法

internal abstract class BaseContinuationImpl(
    // This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet
    // it has a public getter (since even untrusted code is allowed to inspect its call stack).
    // 这个 completion 可能是父协程的协程体
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    // This implementation is final. This fact is used to unroll resumeWith recursion.
    public final override fun resumeWith(result: Result<Any?>) {
        // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
        var current = this
        var param = result
        while (true) {
            // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
            // can precisely track what part of suspended callstack was already resumed
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                    	// 调用协程体
                        val outcome = invokeSuspend(param)
                        // 如果挂起直接返回
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    // 执行恢复逻辑
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }
}

对于getContent方法来讲,没有挂起逻辑,所以执行完invokeSuspend方法后,没有return,当再走到completion.resumeWith方法的时候,getContent的逻辑已经执行完了,接下来就是恢复父协程的操作了、
也就是第二次调用父协程状态机的invokeSuspend方法,此时label已经为1,就执行打印逻辑了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值