首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在Kotlin Coroutine中,暂停函数意味着什么?

在Kotlin Coroutine中,暂停函数意味着什么?
EN

Stack Overflow用户
提问于 2017-12-18 15:47:31
回答 8查看 164.5K关注 0票数 258

我正在阅读Kotlin,并且知道它是基于suspend函数的。但是suspend是什么意思呢?

协同器还是函数被挂起?

来自https://kotlinlang.org/docs/reference/coroutines.html

基本上,coroutines是一种不阻塞线程就可以挂起的计算。

我经常听到人们说“暂停功能”。但我认为是协同线被暂停,因为它正在等待功能完成?“暂停”通常意味着“停止操作”,在这种情况下,协同线是空闲的。

我们应该说这条协同线被暂停了吗?

哪一种协同作用会被暂停?

来自https://kotlinlang.org/docs/reference/coroutines.html

要继续类推,await()可以是一个挂起函数(因此也可以从异步{}块中调用),它挂起一个协同线,直到完成某些计算并返回其结果:

代码语言:javascript
复制
async { // Here I call it the outer async coroutine
    ...
    // Here I call computation the inner coroutine
    val result = computation.await()
    ...
}

它说“在计算完成之前暂停协同线”,但是coroutine就像一个轻量级线程。因此,如果协同线被挂起,如何进行计算?

我们看到awaitcomputation上被调用,所以返回Deferred的可能是async,这意味着它可以启动另一个协同

代码语言:javascript
复制
fun computation(): Deferred<Boolean> {
    return async {
        true
    }
}

这句话说的是,它挂起一个协同线。它的意思是suspend外部async协同线,还是suspend内部computation协同线?

suspend是否意味着当外部async coroutine等待(await)内部computation coroutine完成时,它(外部async coroutine)空闲(因此名为async coroutine)并将线程返回到线程池,当子computation coroutine完成时,它(外部async coroutine)唤醒,从池中取出另一个线程并继续吗?

我提到这个线程的原因是因为https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

当协同线等待时,线程返回到池中,当等待完成时,协同线将在池中的一个空闲线程上恢复。

EN

回答 8

Stack Overflow用户

发布于 2018-10-22 08:23:26

挂起函数是一切协同的中心。挂起函数只是一个可以在以后暂停并恢复的函数。他们可以执行一个长时间运行的操作,并等待它完成而不阻塞。

除了添加suspend关键字之外,挂起函数的语法与常规函数的语法相似。它可以接受一个参数并具有一个返回类型。但是,挂起函数只能由另一个挂起函数或在协同线内调用。

代码语言:javascript
复制
suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

在隐藏模式下,编译器将挂起函数转换为没有挂起关键字的另一个函数,该函数采用Continuation<T>类型的加法参数。例如,上面的函数将由编译器转换为:

代码语言:javascript
复制
fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

Continuation<T>是一个接口,它包含两个函数,它们被调用以使用返回值恢复协同线,如果函数挂起时发生错误,则为异常。

代码语言:javascript
复制
interface Continuation<in T> {
   val context: CoroutineContext
   fun resume(value: T)
   fun resumeWithException(exception: Throwable)
}
票数 288
EN

Stack Overflow用户

发布于 2018-01-05 11:42:04

要理解挂起协同线的确切含义,我建议您通过以下代码:

代码语言:javascript
复制
import kotlinx.coroutines.Dispatchers.Unconfined
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

var continuation: Continuation<Int>? = null

fun main() {
    GlobalScope.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调用的一部分立即开始在当前线程上执行。所发生的情况如下:

  1. 评估val a = a()
  2. 这条链连接到b(),到达suspendCoroutine
  3. 函数b()执行传递给suspendCoroutine的块,然后返回一个特殊的COROUTINE_SUSPENDED值。通过Kotlin编程模型无法观察到这个值,但这正是编译后的Java方法所做的。
  4. 函数a(),看到这个返回值,它本身也会返回它。
  5. launch块执行相同的操作,现在控件在launch调用之后返回到行:10.downTo(0)...

请注意,此时,您的效果与launch块中的代码和fun main代码同时执行的效果相同。这一切发生在一个本地线程上,因此launch块被“挂起”。

现在,在forEach循环代码中,程序读取b()函数编写的continuation,并使用10值对其进行resumesresume()的实现方式就像用传入的值返回suspendCoroutine调用一样。因此,您突然发现自己正在执行b()。传递给resume()的值被分配给i,并根据0进行检查。如果不是零,则while (true)循环在b()中继续进行,再次到达suspendCoroutine,此时您的resume()调用将返回,现在您将在forEach()中执行另一个循环步骤。这种情况一直持续到最后继续使用0,然后运行println语句并完成程序。

上面的分析应该给您一个重要的直觉,即“挂起协同线”意味着将控件返回到最内部的launch调用(或者更广泛地说,是coroutine构建器)。如果协同线在恢复后再次挂起,则resume()调用结束并控制返回给resume()的调用方。

协同调度器的存在使这一推理变得不那么清晰,因为它们中的大多数立即将您的代码提交到另一个线程。在这种情况下,上面的故事发生在另一个线程中,coroutine还管理continuation对象,以便在返回值可用时恢复它。

票数 46
EN

Stack Overflow用户

发布于 2019-12-16 09:41:28

由于已有许多好的答案,我想为其他人举一个简单的例子。

runBlocking用例:

  • myMethod()是suspend函数
  • runBlocking { }以阻塞方式启动Coroutine。这类似于我们如何使用Thread类阻塞普通线程,并在某些事件发生后通知阻塞线程。
  • runBlocking { } 阻塞当前执行线程,直到协同线({}之间的主体)完成为止。 覆盖onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) Log.i(标记,“外部代码开始于线程:”+ Thread.currentThread().name);runBlocking {Log.d(标记,“内部代码开始于线程:”+ Thread.currentThread().name +“使外部代码挂起”);myMethod();}Log.i(标记,“外部代码在线程上继续进行:”+ Thread.currentThread().name);}私有挂起的myMethod() { withContext(Dispatchers.Default) { for(i in 1..5) {Log.d(标记,“内部代码i:线程上的$i:”+ Thread.currentThread().name);}}

这一产出如下:

代码语言:javascript
复制
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,协同线将一直工作到应用程序生存期结束。 覆盖onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) Log.i(标记,“外部代码开始于线程:”+ Thread.currentThread().name);GlobalScope.launch(Dispatchers.Default) {Log.d(标记,“内部代码开始于线程:”+ Thread.currentThread().name +“使外部代码挂起”);myMethod();}Log.i(标记,“外部代码在线程上继续进行:”+ Thread.currentThread().name);}私有挂起的myMethod() { withContext(Dispatchers.Default) { for(i in 1..5) {Log.d(标记,“内部代码i:线程上的$i:”+ Thread.currentThread().name);}

这一产出如下:

代码语言:javascript
复制
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

异步和等待用例:

  • 当我们有多个任务要执行和时,它们依赖于其他任务的完成,asyncawait会提供帮助。
  • 例如,在下面的代码中,有2挂起函数myMethod()和myMethod2()。myMethod2()只有在完全完成myMethod() myMethod2()后才能执行,这取决于myMethod()的结果,我们可以使用asyncawait
  • async并行地启动一个协同线,类似于launch。但是,它提供了一种在并行启动另一个协同线之前等待一个协同线的方法。
  • 那边是await()async返回Deffered<T>的一个实例。默认情况下,T将是Unit。当我们需要等待任何async的完成时,我们需要对该asyncDeffered<T>实例调用.await()。与下面的示例一样,我们调用了innerAsync.await(),这意味着执行将被暂停,直到innerAsync完成为止。我们可以观察到相同的输出。innerAsync先完成,然后调用myMethod()。然后再调用async innerAsync2,它调用myMethod2() 覆盖onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) Log.i(标记,“外部代码开始于线程:”+ Thread.currentThread().name);job = GlobalScope.launch(Dispatchers.Default) { innerAsync =异步{Log.d(标记,“内部代码开始于线程:”+ Thread.currentThread().name +“使外部代码挂起”);myMethod();} innerAsync.await() innerAsync2 =异步{Log.w(标记,“内部代码从线程开始:”+ Thread.currentThread().name +“使外部代码挂起”);myMethod2();}Log.i(标记,“在线程上恢复外部代码:”+ Thread.currentThread().name);}私有挂起myMethod() { withContext(Dispatchers.Default) { for(i in 1..5) {Log.d(标记,“内部代码i:线程上的$i:”+ Thread.currentThread().name);}私有挂起myMethod2() { withContext(Dispatchers.Default) { for(i in 1..10) {Log.w(标记,“内部代码i:线程上的$i:”+ Thread.currentThread().name);}

这一产出如下:

代码语言:javascript
复制
11814-11814/? I/TAG: Outer code started on Thread : main
11814-11814/? I/TAG: Outer code resumed on Thread : main
11814-11845/? D/TAG: Inner code started  on Thread : DefaultDispatcher-worker-2 making outer code suspend
11814-11845/? D/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-2
11814-11845/? D/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-2
// ---- Due to await() call, innerAsync2 will start only after innerAsync gets completed
11814-11848/? W/TAG: Inner code started  on Thread : DefaultDispatcher-worker-4 making outer code suspend
11814-11848/? W/TAG: Inner code i : 1 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 2 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 3 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 4 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 5 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 6 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 7 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 8 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 9 on Thread : DefaultDispatcher-worker-4
11814-11848/? W/TAG: Inner code i : 10 on Thread : DefaultDispatcher-worker-4
票数 33
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/47871868

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档