Kotlin如何在内部实现协程?
协程被称为线程的“较轻版本”,我了解它们在内部使用线程来执行协程。
使用任何构建器功能启动协程时会发生什么?
这是我对运行此代码的理解:
/**
* module.cpp
*/
// Helper class, not visible outside this file.
// Invoked like a function.
class help {
public:
help() { step1(); step2(); step3(); }
private:
void step1();
void step2();
void step3();
int a;
ComplexType b;
vector<ComplexType2> c;
};
// Implementation of help...
// Implementation of the public function
void io_sequence()
{
(void) help();
}
GlobalScope.launch { <---- (A)
val y = loadData() <---- (B) // suspend fun loadData()
println(y) <---- (C)
delay(1000) <---- (D)
println("completed") <---- (E)
}
。 ThreadPool
,Kotlin在下一个可用的空闲线程(说(A)
)中开始执行协程。Thread01
,Kotlin停止执行当前线程,并在下一个可用的空闲线程((B)
)中启动挂起函数loadData()
。Thread02
时,Kotlin在下一个可用的空闲线程中继续协程 ((B)
)。Thread03
在(C)
上执行。Thread03
,(D)
停止了。Thread03
在下一个空闲线程(例如(E)
)上执行。我理解正确吗?还是协程以不同的方式实现?
答案 0 :(得分:7)
协程与您描述的任何调度策略完全不同。协程基本上是suspend fun
的调用链。暂停完全在您的控制之下:您只需致电suspendCoroutine
。您将获得一个回调对象,因此可以调用其resume
方法并返回到挂起的位置。
在某些代码中,您可以看到暂停是一种非常直接和透明的机制,完全在您的控制之下:
import kotlin.coroutines.*
import kotlinx.coroutines.*
var continuation: Continuation<String>? = null
fun main(args: Array<String>) {
val job = GlobalScope.launch(Dispatchers.Unconfined) {
while (true) {
println(suspendHere())
}
}
continuation!!.resume("Resumed first time")
continuation!!.resume("Resumed second time")
}
suspend fun suspendHere() = suspendCancellableCoroutine<String> {
continuation = it
}
您launch
的协程在每次调用suspendHere()
时都会自行挂起。它将继续回调写入continuation
属性,然后您显式地使用该继续来恢复协程。
代码使用Unconfined
协程分派器,它根本不分派线程,它只是在您调用continuation.resume()
的地方运行协程代码。
考虑到这一点,让我们重新查看一下图:
GlobalScope.launch { <---- (A)
val y = loadData() <---- (B) // suspend fun loadData()
println(y) <---- (C)
delay(1000) <---- (D)
println("completed") <---- (E)
}
- 科特琳开头有一个预定义的
ThreadPool
。
它可能有也可能没有线程池。 UI调度程序使用单个线程。
使线程成为协程调度程序的目标的先决条件是,存在与之关联的并发队列,并且该线程运行一个顶级循环,该循环从此队列中获取Runnable
个对象并执行它们。协程调度员只需将继续操作放在该队列中即可。
- 在
(A)
,Kotlin在下一个可用的空闲线程(说Thread01
)中开始执行协程。
它也可以与您调用launch
的线程相同。
- 在
(B)
,Kotlin停止执行当前线程,并在下一个可用的空闲线程(loadData()
)中启动挂起函数Thread02
。
Kotlin无需停止任何线程即可挂起协程。实际上,协程的主要目的是不要启动或停止线程。线程的顶级循环将继续并选择另一个可运行的线程。
此外,仅在呼叫suspend fun
的事实就没有任何意义。协程仅在显式调用suspendCoroutine
时才会挂起自身。该函数也可以简单地返回而不会暂停。
但是假设它确实调用了suspendCoroutine
。在这种情况下,协程不再在任何线程上运行 。它已被挂起,直到某些代码在某处调用continuation.resume()
之前无法继续。该代码可以在将来的任何时间在任何线程上运行。
- 执行后返回
(B)
时,Kotlin在下一个可用的空闲线程中继续协程 (Thread03
)。
B
不会“执行后返回”,协程仍在其体内时恢复。返回之前,它可能会暂停并恢复任意次数。
(C)
在Thread03
上执行。- 在
(D)
,Thread03
停止了。- 1000毫秒后,
(E)
在下一个空闲线程(例如Thread01
)上执行。
同样,没有线程被停止。协程被挂起,通常针对调度程序的机制用于调度1000 ms之后的恢复。届时它将被添加到与调度程序关联的运行队列中。
为具体起见,让我们来看一些示例,这些示例说明了派出协程使用哪种代码。
Swing UI调度程序:
EventQueue.invokeLater { continuation.resume(value) }
Android UI调度程序:
mainHandler.post { continuation.resume(value) }
ExecutorService调度程序:
executor.submit { continuation.resume(value) }
答案 1 :(得分:4)
协程通过在可能的恢复点之间进行切换来工作:
class MyClass$Coroutine extends CoroutineImpl {
public Object doResume(Object o, Throwable t) {
switch(super.state) {
default:
throw new IllegalStateException("call to \"resume\" before \"invoke\" with coroutine");
case 0: {
// code before first suspension
state = 1; // or something else depending on your branching
break;
}
case 1: {
...
}
}
return null;
}
}
执行此协程的结果代码随后将创建该实例,并在每次需要恢复执行时调用doResume()
函数,其处理方式取决于用于执行的调度程序。
这是一个简单的协程的示例编译:
launch {
println("Before")
delay(1000)
println("After")
}
哪个会编译为此字节码
private kotlinx.coroutines.experimental.CoroutineScope p$;
public final java.lang.Object doResume(java.lang.Object, java.lang.Throwable);
Code:
0: invokestatic #18 // Method kotlin/coroutines/experimental/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED:()Ljava/lang/Object;
3: astore 5
5: aload_0
6: getfield #22 // Field kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.label:I
9: tableswitch { // 0 to 1
0: 32
1: 77
default: 102
}
32: aload_2
33: dup
34: ifnull 38
37: athrow
38: pop
39: aload_0
40: getfield #24 // Field p$:Lkotlinx/coroutines/experimental/CoroutineScope;
43: astore_3
44: ldc #26 // String Before
46: astore 4
48: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream;
51: aload 4
53: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
56: sipush 1000
59: aload_0
60: aload_0
61: iconst_1
62: putfield #22 // Field kotlin/coroutines/experimental/jvm/internal/CoroutineImpl.label:I
65: invokestatic #44 // Method kotlinx/coroutines/experimental/DelayKt.delay:(ILkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
68: dup
69: aload 5
71: if_acmpne 85
74: aload 5
76: areturn
77: aload_2
78: dup
79: ifnull 83
82: athrow
83: pop
84: aload_1
85: pop
86: ldc #46 // String After
88: astore 4
90: getstatic #32 // Field java/lang/System.out:Ljava/io/PrintStream;
93: aload 4
95: invokevirtual #38 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
98: getstatic #52 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
101: areturn
102: new #54 // class java/lang/IllegalStateException
105: dup
106: ldc #56 // String call to \'resume\' before \'invoke\' with coroutine
108: invokespecial #60 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
111: athrow
我使用kotlinc 1.2.41对此进行了编译
从32到76是打印Before
并调用挂起的delay(1000)
的代码。
从77到101是用于打印After
的代码。
从102到111是错误恢复状态的错误处理,如切换表中的default
标签所示。
因此,作为总结,kotlin中的协程仅仅是由某些调度程序控制的状态机。