我正在阅读Kotlin Coroutine并知道它基于suspend
功能。但suspend
是什么意思?
协程或功能被暂停?
来自https://kotlinlang.org/docs/reference/coroutines.html
基本上,协同程序是可以在不阻塞线程的情况下暂停的计算
我听说人们经常说"暂停功能"。但是我认为正在等待函数完成的是协同程序被暂停? "暂停"通常意味着"停止操作",在这种情况下,协程是空闲的。
我们应该说协程被暂停吗?
哪个协程被暂停?
来自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
上看到computation
被调用,因此可能async
返回Deferred
,这意味着它可以启动另一个协程
fun computation(): Deferred<Boolean> {
return async {
true
}
}
引用说明 暂停协程 。这是suspend
外部async
协程,还是suspend
内部computation
协程?
suspend
是否意味着外部async
协程正在等待(await
)内部computation
协程完成,它(外部async
协程)idles(因此名称为suspend)并将线程返回给线程池,当子computation
协程完成时,它(外部async
协同程序)唤醒,从池中获取另一个线程并继续?
我提到帖子的原因是https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html
当协程正在等待时,线程返回到池中;等待完成后,协程将在池中的空闲线程上恢复
答案 0 :(得分:21)
暂停功能是所有协程的核心。 暂停功能只是可以在以后暂停和恢复的功能。他们可以执行长时间运行的操作,并等待其完成而不会阻塞。
除了增加了suspend关键字之外,suspending函数的语法与常规函数的语法相似。它可以带有一个参数并具有返回类型。但是,挂起函数只能由另一个挂起函数或在协程中调用。
suspend fun backgroundTask(param: Int): Int {
// long running operation
}
在后台,挂起函数由编译器转换为不带suspend关键字的另一个函数,该函数采用Continuation类型的附加参数。例如,上面的函数将由编译器转换为:
fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
// long running operation
}
Continuation是一个接口,其中包含两个函数,这些函数被调用以使用返回值或异常(如果在函数暂停期间发生错误)来恢复协程。
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
答案 1 :(得分:12)
要了解暂停协程的确切含义,我建议您仔细阅读以下代码:
import kotlinx.coroutines.experimental.Unconfined
import kotlinx.coroutines.experimental.launch
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.suspendCoroutine
var continuation: Continuation<Int>? = null
fun main(args: Array<String>) {
launch(Unconfined) {
val a = a()
println("Result is $a")
}
10.downTo(0).forEach {
continuation!!.resume(it)
}
}
suspend fun a(): Int {
return b()
}
suspend fun b(): Int {
while (true) {
val i = suspendCoroutine<Int> { cont -> continuation = cont }
if (i == 0) {
return 0
}
}
}
Unconfined
协程调度程序消除了协程调度的魔力,并允许我们直接关注裸协同程序。
作为launch
调用的一部分,launch
块内的代码立即开始在当前线程上执行。会发生什么如下:
val a = a()
b()
,到达suspendCoroutine
。b()
执行传递给suspendCoroutine
的块,然后返回特殊的COROUTINE_SUSPENDED
值。这个值不能通过Kotlin编程模型观察到,但这就是编译后的Java方法所做的。a()
,看到此返回值,本身也会返回它。launch
块执行相同操作,现在控件返回到launch
调用后的行:10.downTo(0)...
请注意,此时,您的效果与launch
块中的代码和fun main
代码同时执行的效果相同。恰好所有这一切都发生在一个本机线程上,因此launch
块被“暂停”。
现在,在forEach
循环代码中,程序会读取continuation
函数写入的b()
和resumes
,其值为10
。 resume()
的实现方式就像suspendCoroutine
调用返回时传入的值一样。所以你突然发现自己正在执行b()
。您传递给resume()
的值会被分配到i
并针对0
进行检查。如果它不为零,则while (true)
循环在b()
内继续,再次到达suspendCoroutine
,此时您的resume()
调用将返回,现在您将进入另一个循环步骤forEach()
。这一直持续到最后你用0
继续,然后println
语句运行并且程序完成。
上面的分析应该给你一个重要的直觉,即“暂停协程”意味着将控件返回到最里面的launch
调用(或者更常见的是,协程构建器)。如果协程在恢复后再次暂停,则resume()
呼叫结束,控制权返回resume()
的呼叫者。
协程调度程序的存在使得这种推理不太明确,因为大多数人立即将您的代码提交给另一个线程。在这种情况下,上述故事发生在另一个线程中,协程调度程序也管理continuation
对象,以便在返回值可用时恢复它。
答案 2 :(得分:4)
协程或功能被暂停?
调用暂停 ing 函数将<协程>暂停 s ,这意味着当前线程可以开始执行另一个协程。因此,据说协程被暂停,而不是功能。
但是从技术上讲,此时您的函数将不会由另一个协程执行,因此我们可以说该函数和协程都停止了,但是我们在这里将头发分开。
哪些协程被暂停?
外部async
启动一个协程。调用computation()
时,内部async
启动第二个协程。然后,对await()
的调用将暂停外部 async
协程的执行,直到执行 inner async
协程结束了。
您甚至可以看到一个线程:该线程将执行外部async
的开始,然后调用computation()
并到达内部async
。此时,内部异步的主体被跳过,线程继续执行外部async
直到到达await()
。
await()
是一个“暂停点”,因为await
是一个暂停功能。
这意味着外部协程已暂停,因此线程开始执行内部协程。完成后,返回执行外部async
的结尾。
是否挂起表示在外部异步协程等待(等待)内部计算协程完成时,它(外部异步协程)空闲(因此名称为suspend)并将线程返回到线程池,以及当子级计算协程完成,它(外部异步协程)醒来,从池中获取另一个线程并继续?
是的,
答案 3 :(得分:1)
由于已经有了许多好的答案,我想为其他人提供一个更简单的示例。
runBlocking 用例:
suspend
函数runBlocking { }
以阻止方式启动协程。这类似于我们使用Thread
类阻塞普通线程并在某些事件之后通知阻塞线程的方式。 runBlocking { }
会阻塞当前执行的线程,直到协程({}
之间的主体)完成
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
runBlocking {
Log.d(TAG,"Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend");
myMethod();
}
Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
}
private suspend fun myMethod() {
withContext(Dispatchers.Default) {
for(i in 1..5) {
Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
}
}
此输出:
I/TAG: Outer code started on Thread : main
D/TAG: Inner code started on Thread : main making outer code suspend
// ---- main thread blocked here, it will wait until coroutine gets completed ----
D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- main thread resumes as coroutine is completed ----
I/TAG: Outer code resumed on Thread : main
启动用例:
launch { }
同时启动协程。 worker
线程上开始执行。worker
线程和外线程(我们称之为launch { }
)都同时运行。在内部,JVM可能会执行抢占式线程 当我们需要多个任务并行运行时,我们可以使用它。 scopes
指定了协程的寿命。如果我们指定GlobalScope
,则协程将一直工作到应用程序生命周期结束。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
GlobalScope.launch(Dispatchers.Default) {
Log.d(TAG,"Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend");
myMethod();
}
Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
}
private suspend fun myMethod() {
withContext(Dispatchers.Default) {
for(i in 1..5) {
Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
}
}
}
此输出:
10806-10806/com.example.viewmodelapp I/TAG: Outer code started on Thread : main
10806-10806/com.example.viewmodelapp I/TAG: Outer code resumed on Thread : main
// ---- In this example, main had only 2 lines to execute. So, worker thread logs start only after main thread logs complete
// ---- In some cases, where main has more work to do, the worker thread logs get overlap with main thread logs
10806-10858/com.example.viewmodelapp D/TAG: Inner code started on Thread : DefaultDispatcher-worker-1 making outer code suspend
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-1
10806-10858/com.example.viewmodelapp D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-1
异步和等待用例:
async
和await
会有所帮助。 2
个挂起函数myMethod()和myMethod2()。 myMethod2()
仅在完全完成myMethod()
后才能执行。 OR myMethod2()
取决于myMethod()
的结果,我们可以使用async
和await
async
与launch
类似地并行启动协程。但是,它提供了一种在并行启动另一个协程之前等待一个协程的方法。那样就是await()
。 async
返回Deffered<T>
的实例。默认情况下,T
为Unit
。当我们需要等待任何async
完成时,需要在该.await()
的{{1}}实例上调用Deffered<T>
。像下面的示例一样,我们调用了async
,这意味着执行将暂停,直到innerAsync.await()
完成为止。我们可以在输出中观察到相同的结果。 innerAsync
首先完成,然后调用innerAsync
。然后下一个myMethod()
async
开始,它调用innerAsync2
myMethod2()
此输出:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
Log.i(TAG,"Outer code started on Thread : " + Thread.currentThread().name);
job = GlobalScope.launch(Dispatchers.Default) {
innerAsync = async {
Log.d(TAG, "Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend");
myMethod();
}
innerAsync.await()
innerAsync2 = async {
Log.w(TAG, "Inner code started on Thread : " + Thread.currentThread().name + " making outer code suspend");
myMethod2();
}
}
Log.i(TAG,"Outer code resumed on Thread : " + Thread.currentThread().name);
}
private suspend fun myMethod() {
withContext(Dispatchers.Default) {
for(i in 1..5) {
Log.d(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
}
}
}
private suspend fun myMethod2() {
withContext(Dispatchers.Default) {
for(i in 1..10) {
Log.w(TAG,"Inner code i : $i on Thread : " + Thread.currentThread().name);
}
}
}
答案 4 :(得分:1)
这里有很多很好的答案,但我认为还有两点需要注意。
启动/ 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)
我认为了解它的一种重要方法是查看编译器对这段代码的处理方式。实际上,它为兰巴创建了一个类。它在类中为“ extra”字符串创建一个属性,然后创建两个函数,一个函数打印“ before”,另一个函数打印“ after”。
有效地,编译器采用了类似于程序代码的代码,并将其转换为回调。
那么“ suspend”关键字有什么作用?它告诉编译器向后寻找生成的回调将需要的上下文。编译器需要知道在哪些“回调”中使用了哪些变量,而suspend关键字对此有所帮助。在此示例中,在挂起之前和之后都使用“额外”变量。因此需要将其拉到包含编译器进行回调的类的适当位置。
它还告诉编译器这是状态的“开始”,并准备将以下代码拆分为回调。 “ startCourtine”仅存在于暂停的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");
}
答案 5 :(得分:1)
仍然遇到这个问题的任何人,我建议您快速浏览一下。我已经阅读了很多关于这个问题的误导性答案,甚至是一些评分最高的答案。这消除了我的很多疑虑。
答案 6 :(得分:0)
我想给你一个关于延续概念的简单例子。这是暂停功能可以冻结/暂停然后继续/继续的功能。停止在线程和信号量方面考虑协程。从连续性甚至回调挂钩的角度考虑它。
需要明确的是,可以通过使用可疑函数来暂停法院。让我们对此进行调查:
在android中,我们可以例如:
var TAG = "myTAG:"
fun myMethod() {
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() {
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
想象它是这样工作的:
因此您启动的当前功能不会停止,只有协程会在继续运行时挂起。通过运行暂停功能不会暂停线程。
我认为this site can help可以帮助您解决问题,是我的参考。
let做一些很酷的事情,并在迭代过程中冻结我们的暂停功能。我们将稍后在onResume中恢复它:
存储一个名为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()
}
,循环将继续。很高兴知道我们可以随时冻结一个暂停函数,并在一段时间后恢复它。您还可以查看channels
答案 7 :(得分:0)
我发现理解suspend
的最好方法是在this
关键字和coroutineContext
属性之间进行类比。
Kotlin函数可以声明为局部或全局。本地函数神奇地可以访问this
关键字,而全局则不能。
Kotlin函数可以声明为suspend
或阻塞。 suspend
函数可以神奇地访问coroutineContext
属性,而阻塞函数则不能。
问题是:coroutineContext
属性
is declared like a "normal" property在Kotlin stdlib中,但是此声明只是用于文档/导航目的的存根。实际上,coroutineContext
是builtin intrinsic property,这意味着在编译器内部魔术师意识到此属性,就像它意识到语言关键字一样。
this
关键字对本地函数的作用是coroutineContext
属性对suspend
函数的作用:它提供对当前执行上下文的访问-在第一种情况下可以访问类实例上下文,并且可以协程实例上下文在第二种情况下。
因此,您需要suspend
才能访问coroutineContext
属性-当前执行的协程上下文的实例