协程简单使用
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/