协程简单理解

161 阅读3分钟

协程简单使用

1.先看一段简单的协程代码:

fun test() {
    GlobalScope.launch {
        val res = withContext(Dispatchers.IO) {
            foo()
        }
        println(res)
    }
}

private suspend fun foo(): String {
    delay(2000)
    return "123"
}

2.使用AndroidStudio,Tools-->Kotlin--->show kotlin bytecode ---> decompile查看编译后之后的java代码,先有一个宏观的认识,当然,第一次看肯定一脸懵,下面有源码分析。

public final void test() {
      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 var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            Object var10000;
            switch(this.label) {
            case 0:
               ResultKt.throwOnFailure($result);
               CoroutineContext var5 = (CoroutineContext)Dispatchers.getIO();
               Function2 var10001 = (Function2)(new Function2((Continuation)null) {
                  int label;

                  @Nullable
                  public final Object invokeSuspend(@NotNull Object $result) {
                     Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                     Object var10000;
                     switch(this.label) {
                     case 0:
                        ResultKt.throwOnFailure($result);
                        Test var3 = Test.this;
                        this.label = 1;
                        var10000 = var3.foo(this);
                        if (var10000 == var2) {
                           return var2;
                        }
                        break;
                     case 1:
                        ResultKt.throwOnFailure($result);
                        var10000 = $result;
                        break;
                     default:
                        throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                     }

                     return var10000;
                  }

                  @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);
                  }
               });
               this.label = 1;
               var10000 = BuildersKt.withContext(var5, var10001, this);
               if (var10000 == var4) {
                  return var4;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            String res = (String)var10000;
            boolean var3 = false;
            System.out.println(res);
            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);
   }

协程简单分析

1.先点进launch函数看一下,构建了一个coroutine,然后调用start函数

start()交给了CoroutineStart,这个时候的start实际上是调用了 start.invoke函数,继续跟下去

public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
    start(block, receiver, this)
}

2.很好理解,继续跟下去

public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
        when (this) {
            DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            ATOMIC -> block.startCoroutine(receiver, completion)
            UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            LAZY -> Unit // will start lazily
        }

3.下面就是核心代码了,intercepted().resumeWith()

public fun <R, T> (suspend R.() -> T).startCoroutine(
    receiver: R,
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}

4.BaseContinuationImpl, 里面是一个while循环,最后调用 invokeSuspend 方法。回调到了编译之后的代码了。

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).
    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
                }
            }
        }
    }

5.withContext本身也是开启一个协程,核心也是 resume()--->invokeSuspend()

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
        // compute new context
        val oldContext = uCont.context
        val newContext = oldContext + context
        // always check for cancellation of new context
        newContext.ensureActive()
        // FAST PATH #1 -- new context is the same as the old one
        if (newContext === oldContext) {
            val coroutine = ScopeCoroutine(newContext, uCont)
            return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
        }
        // FAST PATH #2 -- the new dispatcher is the same as the old one (something else changed)
        // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher)
        if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
            val coroutine = UndispatchedCoroutine(newContext, uCont)
            // There are changes in the context, so this thread needs to be updated
            withCoroutineContext(newContext, null) {
                return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
            }
        }
        // SLOW PATH -- use new dispatcher
        val coroutine = DispatchedCoroutine(newContext, uCont)
        block.startCoroutineCancellable(coroutine, coroutine)
        coroutine.getResult()
    }
}

协程简单总结

1.cps变换,遇到suspend函数,自动添加参数continuation,是一个接口,里面持有CoroutineContext。

2.即使kotlin编译器做了再多东西,也不能脱离代码运行基本规则————按编译之后的代码顺序。

3.执行顺序

kotlin中的startCoroutine -----> 编译之后的java文件里的invokeSuspend() ----> 里面 是一个switch-case,那就执行switch-case,默认是0,执行状态0的代码,状态0代码里面使用 withContext开启一个新的协程,使用BuildersKt.withContext()开始执行逻辑,继续调用里层的invokeSuspend,依然是switch-case状态机模式,执行里层状态0的逻辑,这个时候调用了 foo()方法,等foo()方法执行结束,然后执行里层switch-case下面的代码,里层结束,回调到外层的switch-case状态0执行结束,执行switch-case下面的代码。

4.切换线程的逻辑封装到了CoroutineContext里面,外层没有回调,其实是kotlin编译器帮我们做了回调,而且使用swithc-case+while循环让编译之后的代码更加耐读。

5.协程设计理念:结构化并发,按照顺序执行代码,而不应该经常跳转。

6.协程和RxJava的对比,RxJava将代码限定在一个特定的链里了,事实上用顺序执行,更易于理解代码。

7.协程设计者的博客:elizarov.medium.com/