协程是 Kotlin 的一个概念。你并不会将任务发送到“主线程”,而是发送到了“Main 协程调度程序”(虽然在幕后,你确实是从主线程调用该代码)。
Kotlin 只是一种编程语言,它稍后会被编译成不同的其他语言(Java 和 Android 的 JVM 字节码,本机目标的 LLVM 以及浏览器或 NodeJS 目标的 JS)。
无论目标是什么,概念都保持不变:Kotlin 协程。
例如,在 JavaScript 世界中,我们没有线程:我们只有同步和异步任务堆栈。如果编译到像 Arduino 或 ESP32 这样的嵌入式设备上,情况也是如此。
对于 Android 协程实现,它使用了 Executors
、Handlers
和 Looper
API。它并不是“神奇地注入到了主线程”。但你所提供的代码最终会执行以下操作:
Handler(Looper.getMainLooper()).post({})
因此,在 Android 上,Main
协程调度程序最终会将任务(您的 lambda)分派(传递)到 Android 上的 Main Looper。
如果该代码在浏览器(JavaScript)上运行,低级实现将完全不同。 我想它可能是这样的(但我们必须在 GitHub 上检查源代码):
new Promise((res) => res(yourLambda()))
但协程的概念保持不变:我们将任务传递给Main协程调度器。
最后,回答你的问题:
为什么可以在主线程上运行Kotlin协程?
有些代码(或任务)是可以安全地运行(或必须在)主线程上的,例如操作View的属性。为了实现这一点,我们将这些任务委派给Main协程调度器,该调度器最终会在主线程上运行您的代码。
这段代码可以在主线程上运行;然而,它被标记为挂起代码。同样,在记录日志时也会发生类似的情况。它们可以在挂起上下文中或在其外部运行。
suspend fun makeGone(view: View) {
view.visibility = View.GONE
}
suspend
标记函数并不是真正的关键,它只要求你在 CoroutineContext
或 CoroutineScope
内运行它。
不过需要注意的是:如果你在主线程中运行 IO 代码,Android 将会崩溃:
override fun onCreate() {
launch(Dispatcher.Main) { somethingThatDoesIO() }
}
即使somethingThatDoesIO
未标记为suspend,此代码也会崩溃,因为它在主线程上执行IO操作。
如果我们将somethingThatDoesIO
重新实现如下:
suspend fun somethingThatDoesIO() = withContext(Dispatchers.IO) {
}
以下是翻译的结果:
将要发生的事情是:
- 您向主协程调度程序派发任务
- 主协程调度程序将执行lambda表达式
- 它会意识到
somethingThatDoesIO
需要由IO调度程序运行,并“交出一切所需的”(我知道这非常高级)
- 当
Dispatchers.IO
完成任务后,将控制权交给主调度程序,后者将继续执行lambda中的代码。
我不确定我是否帮助了您或使事情更加混乱。请在评论中让我知道。
Dispatchers
是其他东西,但是通过阅读文档(通过IDE),它明确表示它将在主线程上运行,其中UI操作可用,这非常有趣。 - cutiko