为什么你可以 运行 主线程上的 Kotlin 协程?

Why can you run a Kotlin coroutine on the main thread?

我无法理解为什么 this piece of code可以正常工作:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    launch(Dispatchers.Main) {
        log("A")
    }

    log("B")
}

应该先输出B,然后输出A

这行得通吗,因为主线程已经被协程控制了?还是协同程序 API 以某种方式神奇地将代码注入主线程?

有时您需要来自异步源的数据。此外,如果没有这些数据,您将无法继续,例如从房间数据库或在线服务器获取登录用户信息,在这种情况下,您通常会阻塞主线程,以便获得所需的数据,然后继续进行。

协程是 Kotlin 的一个概念。而且您不是将任务发送到“主线程”,而是发送到 Main coroutine Dispatcher。 (虽然在幕后您会看到您确实是从主线程调用该代码)。

Kotlin 只是一种编程语言,稍后可以编译成不同的其他语言(Java 和 Android 的 JVM 字节码,本机目标的 LLVM 和浏览器或 NodeJS 目标的 JS)。

无论目标是什么,概念都是一样的:Kotlin 协程。

例如:在Java脚本世界中我们没有线程:我们只有同步和异步任务堆栈。如果我们为 Arduino 或 ESP32 等嵌入式设备编译,情况相同。

对于 Android 协程实现,它使用 ExecutorsHandlersLooper API。它不会“神奇地将自己注入主线程”。但是您提供的代码最终会执行以下操作:

Handler(Looper.getMainLooper()).post({/*your lambda here*/})

所以 Main 在 Android 上的 Main 协程调度程序最终将 dispatch (传递)一个任务(你的lambda) 到 Main Looper on Android.

如果该代码是 运行 在浏览器上(Java脚本),那么在 low-level 实现中将是另一回事。我想它会是这样的(但我们必须在 github 处检查源代码):

new Promise((res) => res(yourLambda()))

但协程的概念保持不变:我们将任务传递给主协程调度程序。

最后,回答你的问题:

Why can you run a Kotlin coroutine on the main thread?

在主线程上有 运行(或必须 运行)安全的代码(或任务):例如操作视图的属性。为此,我们将这些任务委托给 Main 协程调度程序,最终将 运行 您的代码放在主线程上。

此代码在主线程上有效 运行;但它被标记为暂停代码。您的日志记录语句也会发生同样的情况。它们在挂起上下文或挂起上下文之外都有效 运行。

suspend fun makeGone(view: View) {
  view.visibility = View.GONE
}

函数被标记为 suspend 的事实并不重要:它只需要您在 CoroutineContextCoroutineScope 中 运行 它即可。

现在这样说:请记住,如果您 运行 来自主线程的 IO 代码:

Android 将会崩溃
override fun onCreate() {
  launch(Dispatcher.Main) { somethingThatDoesIO() }
}

即使 somethingThatDoesIO 在主线程上执行 IO 操作时未标记为挂起,此代码也会崩溃。

如果我们re-implement somethingThatDoesIO 如下:

suspend fun somethingThatDoesIO() = withContext(Dispatchers.IO) {
  // old code
}

接下来会发生什么:

  1. 您向 Main 协程调度程序调度任务
  2. 主协程调度程序将执行 lambda
  3. 它会意识到 somethingThatDoesIO 需要被 IO 调度器 运行 并“交出它需要的一切”(我知道这是很高级别)
  4. Dispatchers.IO 完成 运行 任务时,它将把控制权交给主调度程序,它将继续使用您的 lambda 中的代码。

我不确定我是帮助了你还是让事情变得更加混乱。在评论中让我知道

Android(以及其他 UI 框架)中的 UI/main 线程 运行 是所谓的 event loop. That means it waits for tasks to be scheduled to it, it has a queue of such tasks and executes them sequentially. For example, when you click on a button, internally onClick action is scheduled to be run on the main thread. But the user is also allowed to schedule their tasks manually, for example by using runOnUiThread() or getMainLooper()

Dispatchers.Main 只是在主线程上安排某些事情的另一种方式。这并不意味着协程可以完全控制主线程,或者它们以某种方式神奇地向主线程注入任何东西。主线程是合作的,它允许调度任务和协程只是使用这个特性。

此外,您在评论中问过,两个日志语句怎么可能 运行 并行,但在同一个线程上。它们不是 运行 并行。 onCreate() 只安排 log("A") 稍后执行,这被添加到队列中。然后 log("B") 被调用,只有当 onCreate() 完成时,主线程才能开始执行 log("A") 块。所以这实际上是顺序的,但不是 top-to-bottom 顺序。