本文适合有协程使用基础并想了解内部原理的开发者
项目地址:kotlinx.coroutines, 分析的版本: v1.3
本文结构
1、什么协程(Coroutines)
2、编译器实现协程的异步操作的同步调用
3、协程上下文切换
4、协程执行效率要高于线程/线程池
5、结构化并发原理
6、总结
一、什么协程(Coroutines)
看下Android文档对协程的定义:协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。
二、编译器实现协程的异步操作的同步调用。
2.1 CPS(Continuation Passing Style)
我们先看一个经典的网络请求并更新UI的例子
private val uiHandler = Handler(Looper.getMainLooper())
fun fetchDocs() {
get("https://developer.android.com") {
result ->
show(result)
}
}
fun get(url: String, onSuccess: (String) -> Unit) {
Thread {
print("进行网络请求 get = $url")
onSuccess("Docs Content")
}.start()
}
fun show(content: String) {
uiHandler.post {
print("显示dialog $content") }
}
从上面例子可以看到我们需要创建子线程进行网络请求,然后通过回调调用show(),show()里面uiHandler切换到UI线程更新UI。能看出我们通过回调来返回异步操作结果。
我们看看经过协程改造后的例子
fun fetchDocs() {
CoroutineScope(Dispatchers.Main).launch {
// Dispatchers.Main
val result = get("https://developer.android.com") // Dispatchers.IO for `get`
show(result) // Dispatchers.Main
}
}
suspend fun get(url: String) = withContext(Dispatchers.IO) {
print("进行网络请求 get = $url")
return@withContext "success"
}
fun show(content: String) {
print("显示dialog $content")
}
我们看到get()的回调不见了,使用withContext(Dispatchers.IO)指定在IO线程执行,并同步方式的返回请求结果,show()将结果更新到UI上。
协程使用了什么魔法把异步的调用变成同步调用呢?
我们把上面代码反编译成Java代码,看看他施了什么魔法,代码如下
public final class CoroutineTest2Kt {
public static final void fetchDocs() {
BuildersKt.launch$default(CoroutineScopeKt.CoroutineScope(Dispatchers.getMain()), null, null, new Function2((Continuation)null) {
private CoroutineScope p$;
Object L$0;
// 初始状态 label = 0
int label;
// 该调用在Dispatchers.Main
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
Object var10000;
CoroutineScope $this$launch;
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
$this$launch = this.p$;
this.L$0 = $this$launch;
// 将label切换为1
this.label = 1;
// 把本continuation传给get(),并挂起自身continuation
var10000 = CoroutineTest2Kt.get("https://developer.android.com", this);
if (var10000 == var4) {
return var4;
}
break;
case 1:
$this$launch = (CoroutineScope)this.L$0;
ResultKt.throwOnFailure($result);
var10000 = $result;
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
// resumeWhite()重新开始,继续执行,在UI显示
String result = (String)var10000;
CoroutineTest2Kt.show(result);
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
Intrinsics.checkParameterIsNotNull(completion, "completion");
Function2 var3 = new <anonymous constructor>(completion);
var3.p$ = (CoroutineScope)value;
return var3;
}
public final Object invoke(Object var1, Continuation var2) {
return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
}
}), 3, (Object)null);
}
@Nullable
public static final Object get(@NotNull final String url, @NotNull Continuation $completion) {
return BuildersKt.withContext((CoroutineContext)Dispatchers.getIO(), (Function2)(new Function2((Continuation)null) {
private CoroutineScope p$;
int label;
// 该调用在Dispatchers.IO
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(this.label) {
case 0:
// 在IO中处理数据
ResultKt.throwOnFailure($result);
CoroutineScope $this$withContext = this.p$;
String var3 = "进行网络请求 get = " + url;
boolean var4 = false;
System.out.print(var3);
// 返回处理成功
return "success";
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
Intrinsics.checkParameterIsNotNull(completion, "completion");
Function2 var3 = new <anonymous constructor>(completion);
var3.p$ = (CoroutineScope)value;
return var3;
}
public final Object invoke(Object var1, Object var2) {
return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
}
}), $completion);