【问题标题】:Do suspend functions execute in another thread and come back with the result?挂起函数是否在另一个线程中执行并返回结果?
【发布时间】:2020-08-06 07:23:54
【问题描述】:

我在网上看了很多文章,但我仍然有点困惑,当挂起函数在协程中被挂起时,具体会发生什么,一步一步地发生。

我知道挂起函数,在底层,只是一个常规函数,带有一个允许它恢复的 Continuation 参数,但我的困惑是关于该挂起函数或协程的去向以及恢复后它返回的位置。

我听过一些人说“他们不一定会回到同一个线程”,我不明白,谁能给我逐步解释一下?

【问题讨论】:

标签: android kotlin kotlin-coroutines


【解决方案1】:

TLDR;

没有保证,它可能会或可能不会, 这真的取决于以下几点:

  1. 调度程序是多线程的吗?
  2. 两者之间是否有任何调度程序覆盖?

长答案

协程有一个 CoroutineContext 来指定它的行为方式和运行位置。

一个CoroutineContext主要由四个元素组成:JobCoroutineNameCoroutineExceptionHandlerDispatcher

调度器负责调度协程。可以暂停调度程序以停止协程甚至运行(这在单元测试中很有用)mentioned here in the android conference talk,它可能是像 Dispatchers.Main 一样的单线程调度程序,它有一个类似 javascript 的事件循环。

所以,这真的取决于以下几点:

  1. 调度程序是多线程的吗?

例如:这将在单线程上运行。

suspend fun main() {
    val dispatcherScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher())

    val job = dispatcherScope.launch {
        repeat(10) {
            launch {
                println("I'm working in thread ${Thread.currentThread().name}")
                // every coroutine on same thread
            }
        }
    }
    job.join()
}

Run it here,其他单线程调度器:Dispatchers.Main

  1. 两者之间是否有任何调度程序覆盖?

在相同的上下文下,如果我们在启动前重写调度器,即使原来的上下文是基于单线程事件循环的,它也会改变线程,每个协程将运行在不同的线程上,创建10个不同的线程:

dispatcherScope.launch {
    repeat(10) {
        launch(Dispatchers.IO) {
            println("I'm working in thread ${Thread.currentThread().name}")
            // every coroutine on same thread
        }
    }
}

Run it here,其他多线程调度程序:Dispatchers.Default,基于执行程序的调度程序,Dispatchers.Unconfined(此在任何空闲线程中启动协程)。

【讨论】:

    【解决方案2】:

    简短的回答是:他们执行 somewhere 并将结果返回到 somewhere

    简短答案的详细解释:

    “Somewhere”可能是同一个线程,也可能是不同的线程——它取决于调度程序,在许多情况下,取决于调度程序的当前状态。例如,SequenceScope 的内容将(默认)在同一个线程上运行。

    挂起函数可能在同一线程上运行的另一种情况是,它使用与调用函数相同的调度程序。

    Dispatchers 也可以在它们之间共享线程以节省线程创建(并且只计算它们自己的任务的最大并行操作数),因此即使在每个使用线程池的不同 Dispatcher 之间切换也可能不会导致使用不同的线程。

    就人们说“他们不一定会回到同一个线程”而言,这是正确的,原因类似。请记住,您的调度程序可能有很多线程,而您在被挂起之前使用的线程现在可能正在使用不同的功能,因此调度程序将简单地选择一个不同的线程来运行您的代码。

    【讨论】:

    • 谢谢。我知道挂起乐趣所在的整个协程都会暂停,直到该挂起点被解决(有结果),我只是对那个特定的挂起点是在挂起时暂停还是只是离开线程感到困惑,它是否在另一个线程上工作并返回到同一个线程的结果。我知道调度程序没有在 IO 或默认值中指定任何特定线程,而只是这些类型的线程池。一天结束了,这不可能是魔术,协程需要一个线程来处理。
    • 如果“该特定暂停点在暂停时是否暂停”,您的意思是线程是否阻塞以等待它完成它在另一个线程上所做的任何事情(假设它 确实 i> 转到另一个线程),答案是否定的。挂起函数的想法正是它不会阻塞,它释放线程来做其他工作(此外,在某些情况下它甚至不会回到那个线程)。
    【解决方案3】:

    当协程挂起时,底层 Java 方法返回一个特殊的 COROUTINE_SUSPENDED 值。如果调用函数也是可挂起的,它也返回对象,因此执行返回到最里面的普通的、不可挂起的函数。此函数通常运行一个事件循环,其中事件处理程序是对continuation.resume() 的调用。所以现在它准备好从队列中获取下一个处理程序并恢复另一个协程。

    当您调用continuation.resume() 时,延续本身知道负责协程的调度程序并委托给它。如果当前线程不属于该调度程序,它会将一个事件调度到另一个事件循环,该事件循环由调度程序的线程池提供服务。这样,调度程序就控制了协程恢复的线程。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 2020-02-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多