如何让CoroutineScope(job+Dispatchers.Main)在主/UI线程上运行?

9

如果在CoroutineScope(job+Dispatchers.Main){...}内部的操作在主线程上运行,那么为什么它不违反Android的要求,即不允许在主/UI线程上运行缓慢(阻塞)的操作(如网络等)?我可以使用此作用域运行阻塞操作,而UI根本不会冻结。

如果有人能解释一下底层发生了什么,我将不胜感激。 我猜这类似于JavaScript如何通过事件循环管理阻塞操作,但我难以找到任何相关材料。


“我可以在此范围内运行阻塞操作,而UI不会冻结。”这句话是什么意思?请举个例子。 - Dmitrii Leonov
2
例如,我可以通过调用delay(1000)来暂停线程。如果我在UI线程上这样做,这不应该会导致ANR屏幕出现吗?@DmitriiLeonov - 372
2个回答

5

执行 阻塞 操作和执行 挂起 操作对于 CoroutineScope(Dispatchers.Main) 来说是两个不同的事情。

delay() 是一个挂起函数,它是非阻塞的。

CoroutineScope(Dispatchers.Main){
    delay(6000)
}

Thread.sleep() 是阻塞的,调用下面的代码会导致 ANR

CoroutineScope(Dispatchers.Main){
    Thread.sleep(6000)
}

我建议你查看罗曼·埃利扎罗夫在Kotlinconf 2017上关于Kotlin协程的演讲,特别是他运行100,000个delay()的部分。


感谢您的见解! - 372

5

我猜想它与JavaScript如何使用事件循环处理阻塞操作类似。

是的,这是正确的,事件循环对于使协程工作非常重要。基本上,当你编写如下代码时:

uiScope.launch {
    delay(1000)
    println("A second has passed")
}

它被编译成与以下代码具有相同效果的代码:

Handler(Looper.mainLooper()).postDelayed(1000) { println("A second has passed") }

主要概念是“Continuation”(续体),它实现了一个状态机,对应于您在可暂停函数中编写的顺序代码。当您调用“delay”或任何其他可暂停函数时,续体的入口方法返回一个特殊的“COROUTINE_SUSPENDED”值。稍后,当某些外部代码找到可暂停函数的返回值时,它必须调用“continuation.resume(result)”方法。这个调用将被负责的调度程序拦截,该程序会将此调用作为GUI事件循环上的事件发布。当事件处理程序被出列并执行时,您回到状态机内,该状态机确定从哪里恢复执行。
您可以查看这个答案,以获取更详细的使用“Continuation”API的示例。

谢谢您的解释。我还没有看到有人提供一个等效的基于Handler的示例。 - 372
delay函数是非阻塞的。OP正在询问一个阻塞操作,例如一些图像处理逻辑。 - alekop
@alekop OP:“我可以在这个范围内运行阻塞操作,而UI不会冻结。”“例如,我可以通过调用delay(1000)来暂停线程。” 这意味着他们在更一般的意义上使用“阻塞”这个词,而不是通常的行话。 在这个意义上,它也包括挂起行为。 - Marko Topolnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接