我正在阅读Kotlin协程,并且知道它是基于挂起函数的。但是暂停是什么意思呢?
协程或函数被挂起?
从https://kotlinlang.org/docs/reference/coroutines.html
基本上,协程是可以挂起而不阻塞线程的计算
我经常听到人们说“暂停功能”。但我认为是协程被挂起,因为它正在等待函数完成?“suspend”通常意味着“停止操作”,在这种情况下,协程是空闲的。
我们是否应该说协程被挂起?
哪个协程被挂起?
从https://kotlinlang.org/docs/reference/coroutines.html
继续类比,await()可以是一个挂起函数(因此也可以从async{}块中调用),它挂起协程,直到完成一些计算并返回其结果:
async { // Here I call it the outer async coroutine
...
// Here I call computation the inner coroutine
val result = computation.await()
...
}
它说“这会挂起一个协程,直到一些计算完成”,但协程就像一个轻量级线程。那么,如果协程挂起,如何进行计算呢?
我们看到await在计算时被调用,所以返回Deferred的可能是异步的,这意味着它可以启动另一个协程
fun computation(): Deferred<Boolean> {
return async {
true
}
}
这句话说挂起了一个协程。这意味着挂起外部异步协程,还是挂起内部计算协程?
挂起是否意味着当外部异步协程正在等待(await)内部计算协程完成时,它(外部异步协程)空闲(因此称为挂起)并返回线程池,当子计算协程完成时,它(外部异步协程)醒来,从池中取出另一个线程并继续?
我之所以提到这个线程是因为https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html
当协程正在等待时,线程被返回到池中,当等待完成时,协程在池中的空闲线程上恢复
这里有很多很好的答案,但我认为还有两件重要的事情需要注意。
例子中的launch / withContext / runBlocking和其他很多东西都来自协程库。其实和暂停没有任何关系。使用协程不需要协程库。协程是编译器的一个“技巧”。是的,库确实让事情变得更简单,但编译器正在做挂起和恢复事情的魔法。
第二件事,编译器只是把看起来是过程的代码转换成内部的回调。
取以下最小协程,挂起不使用协程库的协程:
lateinit var context: Continuation<Unit>
suspend {
val extra="extra"
println("before suspend $extra")
suspendCoroutine<Unit> { context = it }
println("after suspend $extra")
}.startCoroutine(
object : Continuation<Unit> {
override val context: CoroutineContext = EmptyCoroutineContext
// called when a coroutine ends. do nothing.
override fun resumeWith(result: Result<Unit>) {
result.onFailure { ex : Throwable -> throw ex }
}
}
)
println("kick it")
context.resume(Unit)
我认为理解它的一个重要方法是看看编译器对这些代码做了什么。实际上,它为lambda创建了一个类。它在类中为“extra”字符串创建了一个属性,然后创建了两个函数,一个打印“之前”,另一个打印“之后”。
实际上,编译器将看起来像过程代码的代码转换为回调。
那么,suspend关键字是做什么的呢?它告诉编译器要回溯到多远的地方去寻找生成的回调所需要的上下文。编译器需要知道在哪个“回调”中使用了哪些变量,而suspend关键字可以帮助它。在这个例子中,“extra”变量在挂起之前和之后都被使用。因此,它需要被拉出到包含编译器所做回调的类的属性中。
它还告诉编译器这是状态的“开始”,并准备将下面的代码分解为回调。startCoroutine只存在于挂起lambda上。
Kotlin编译器生成的实际Java代码在这里。这是一个switch语句而不是回调,但实际上是一样的。首先调用w/ case 0,然后在恢复后调用w/ case 1。
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
var10_2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0: {
ResultKt.throwOnFailure((Object)$result);
extra = "extra";
var3_4 = "before delay " + extra;
var4_9 = false;
System.out.println((Object)var3_4);
var3_5 = this;
var4_9 = false;
var5_10 = false;
this.L$0 = extra;
this.L$1 = var3_5;
this.label = 1;
var5_11 = var3_5;
var6_12 = false;
var7_13 = new SafeContinuation(IntrinsicsKt.intercepted((Continuation)var5_11));
it = (Continuation)var7_13;
$i$a$-suspendCoroutine-AppKt$main$1$1 = false;
this.$context.element = it;
v0 = var7_13.getOrThrow();
if (v0 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
DebugProbesKt.probeCoroutineSuspended((Continuation)var3_5);
}
v1 = v0;
if (v0 == var10_2) {
return var10_2;
}
** GOTO lbl33
}
case 1: {
var3_6 = this.L$1;
extra = (String)this.L$0;
ResultKt.throwOnFailure((Object)$result);
v1 = $result;
lbl33:
// 2 sources
var3_8 = "after suspend " + extra;
var4_9 = false;
System.out.println((Object)var3_8);
return Unit.INSTANCE;
}
}
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
这里有很多很好的答案,但我认为还有两件重要的事情需要注意。
例子中的launch / withContext / runBlocking和其他很多东西都来自协程库。其实和暂停没有任何关系。使用协程不需要协程库。协程是编译器的一个“技巧”。是的,库确实让事情变得更简单,但编译器正在做挂起和恢复事情的魔法。
第二件事,编译器只是把看起来是过程的代码转换成内部的回调。
取以下最小协程,挂起不使用协程库的协程:
lateinit var context: Continuation<Unit>
suspend {
val extra="extra"
println("before suspend $extra")
suspendCoroutine<Unit> { context = it }
println("after suspend $extra")
}.startCoroutine(
object : Continuation<Unit> {
override val context: CoroutineContext = EmptyCoroutineContext
// called when a coroutine ends. do nothing.
override fun resumeWith(result: Result<Unit>) {
result.onFailure { ex : Throwable -> throw ex }
}
}
)
println("kick it")
context.resume(Unit)
我认为理解它的一个重要方法是看看编译器对这些代码做了什么。实际上,它为lambda创建了一个类。它在类中为“extra”字符串创建了一个属性,然后创建了两个函数,一个打印“之前”,另一个打印“之后”。
实际上,编译器将看起来像过程代码的代码转换为回调。
那么,suspend关键字是做什么的呢?它告诉编译器要回溯到多远的地方去寻找生成的回调所需要的上下文。编译器需要知道在哪个“回调”中使用了哪些变量,而suspend关键字可以帮助它。在这个例子中,“extra”变量在挂起之前和之后都被使用。因此,它需要被拉出到包含编译器所做回调的类的属性中。
它还告诉编译器这是状态的“开始”,并准备将下面的代码分解为回调。startCoroutine只存在于挂起lambda上。
Kotlin编译器生成的实际Java代码在这里。这是一个switch语句而不是回调,但实际上是一样的。首先调用w/ case 0,然后在恢复后调用w/ case 1。
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
var10_2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0: {
ResultKt.throwOnFailure((Object)$result);
extra = "extra";
var3_4 = "before delay " + extra;
var4_9 = false;
System.out.println((Object)var3_4);
var3_5 = this;
var4_9 = false;
var5_10 = false;
this.L$0 = extra;
this.L$1 = var3_5;
this.label = 1;
var5_11 = var3_5;
var6_12 = false;
var7_13 = new SafeContinuation(IntrinsicsKt.intercepted((Continuation)var5_11));
it = (Continuation)var7_13;
$i$a$-suspendCoroutine-AppKt$main$1$1 = false;
this.$context.element = it;
v0 = var7_13.getOrThrow();
if (v0 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
DebugProbesKt.probeCoroutineSuspended((Continuation)var3_5);
}
v1 = v0;
if (v0 == var10_2) {
return var10_2;
}
** GOTO lbl33
}
case 1: {
var3_6 = this.L$1;
extra = (String)this.L$0;
ResultKt.throwOnFailure((Object)$result);
v1 = $result;
lbl33:
// 2 sources
var3_8 = "after suspend " + extra;
var4_9 = false;
System.out.println((Object)var3_8);
return Unit.INSTANCE;
}
}
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
我想给你们举一个关于延续概念的简单例子。这就是挂起函数所做的,它可以冻结/挂起,然后继续/恢复。不要从线程和信号量的角度来考虑协程。从延续甚至回调钩子的角度来考虑。
需要明确的是,协程可以通过使用挂起函数暂停。让我们来研究一下:
在android中,我们可以这样做,例如:
var TAG = "myTAG:"
fun myMethod() { // function A in image
viewModelScope.launch(Dispatchers.Default) {
for (i in 10..15) {
if (i == 10) { //on first iteration, we will completely FREEZE this coroutine (just for loop here gets 'suspended`)
println("$TAG im a tired coroutine - let someone else print the numbers async. i'll suspend until your done")
freezePleaseIAmDoingHeavyWork()
} else
println("$TAG $i")
}
}
//this area is not suspended, you can continue doing work
}
suspend fun freezePleaseIAmDoingHeavyWork() { // function B in image
withContext(Dispatchers.Default) {
async {
//pretend this is a big network call
for (i in 1..10) {
println("$TAG $i")
delay(1_000)//delay pauses coroutine, NOT the thread. use Thread.sleep if you want to pause a thread.
}
println("$TAG phwww finished printing those numbers async now im tired, thank you for freezing, you may resume")
}
}
}
上面的代码输出如下:
I: myTAG: my coroutine is frozen but i can carry on to do other things
I: myTAG: im a tired coroutine - let someone else print the numbers async. i'll suspend until your done
I: myTAG: 1
I: myTAG: 2
I: myTAG: 3
I: myTAG: 4
I: myTAG: 5
I: myTAG: 6
I: myTAG: 7
I: myTAG: 8
I: myTAG: 9
I: myTAG: 10
I: myTAG: phwww finished printing those numbers async now im tired, thank you for freezing, you may resume
I: myTAG: 11
I: myTAG: 12
I: myTAG: 13
I: myTAG: 14
I: myTAG: 15
想象一下它是这样工作的:
所以你启动的当前函数不会停止,只有协程会在它继续运行时挂起。线程不会通过运行挂起函数暂停。
我认为这个网站可以帮助你把事情弄清楚,是我的参考。
让我们做一些很酷的事情,在迭代过程中冻结我们的挂起函数。稍后我们将在onResume中恢复它
存储一个名为continuation的变量,我们将用coroutines continuation对象加载它:
var continuation: CancellableContinuation<String>? = null
suspend fun freezeHere() = suspendCancellableCoroutine<String> {
continuation = it
}
fun unFreeze() {
continuation?.resume("im resuming") {}
}
现在,让我们回到挂起的函数,让它在迭代过程中冻结:
suspend fun freezePleaseIAmDoingHeavyWork() {
withContext(Dispatchers.Default) {
async {
//pretend this is a big network call
for (i in 1..10) {
println("$TAG $i")
delay(1_000)
if(i == 3)
freezeHere() //dead pause, do not go any further
}
}
}
}
然后在其他地方,如onResume(例如):
override fun onResume() {
super.onResume()
unFreeze()
}
循环将继续。我们可以在任何时候冻结一个挂起函数,并在经过一段时间后恢复它。你也可以查看频道