为什么这个协程会阻塞UI线程?

9

弃用警告

此代码使用了旧的协程 API。如果您使用的是 kotlinx-coroutines 1.1.0 或更新版本,此代码对您没有用处

原始问题如下:

我发现我的 Android 应用程序中的这段特定代码会阻塞 UI 线程:

runBlocking {
    async(CommonPool) {
        Thread.sleep(5000)     
    }.await()
}

textView.text = "Finish!"

我已经在多个任务中使用了协程,它们从未阻塞UI线程,正如文档中所述:

协程提供了一种避免阻塞线程并将其替换为更便宜和更可控的操作的方法:挂起协程

但有趣的是,这段代码:

runBlocking {
    async(CommonPool) {
        launch(CommonPool) {
            Thread.sleep(5000)

            runOnUiThread { textView.text = "Finish!" }
        }
    }.await()
}

表现如预期;不会阻塞,等待五秒钟后打印结果(我需要在 sleep 完成后更新 UI)。

文档说可以独立使用 asynclaunch,它们不需要组合。实际上,async(CommonPool) 就足够了。

那么这里到底发生了什么?为什么只有 async+launch 能够工作?

更新(2021年)

[警告] 此代码使用旧的协程 API。如果您正在使用 kotlinx-coroutines 1.1.0 或更高版本,请忘记此代码。

我的完整示例代码:

class MainActivity : AppCompatActivity() {

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

        setContentView(R.layout.activity_main)

        button1.setOnClickListener {
            runBlocking {
                async(CommonPool) {
                    Thread.sleep(5000L)
                }.await()
            }

            textView1.text = "Finally! I've been blocked for 5s :-("
        }

        button2.setOnClickListener {
            runBlocking {
                async(CommonPool) {
                    launch(CommonPool) {
                        Thread.sleep(5000L)

                        runOnUiThread { textView1.text = "Done! UI was not blocked :-)" }
                    }
                }.await()
            }
        }
    }
}
3个回答

10

注意:此帖子的发布日期早于协程的预发布版本。我已更新调度程序的名称以匹配发布版本。

runBlocking不是在UI线程上启动协程的方式,因为正如其名称所示,它将阻塞宿主线程直到协程完成。您必须在Main上下文中使用launch启动它,然后切换到Default上下文进行重量级操作。您还应该放弃async-await对并改用withContext

button1.setOnClickListener {
    launch(Main) {
        withContext(Default) {
            Thread.sleep(5000L)
        }
        textView1.text = "Done! UI was not blocked :-)"
    }
}

withContext 会暂停协程直到完成,然后在父上下文中恢复它,该上下文为 Main


1

正如文档所述:

[runBlocking] 运行新协程并可中断地阻塞当前线程,直到其完成。此函数不应从协程中使用。它旨在将常规阻塞代码与以暂停风格编写的库连接起来,以在main函数和测试中使用。

如引用所示,它通常用于处理常规协程的测试,也用于main方法以等待协程的完成。

此外,这个教程将帮助理解其使用案例。


0

很抱歉回复晚了,但希望您会觉得这个答案有用。

1- 因为在第一种情况下

runBlocking {
    async(CommonPool) {
        Thread.sleep(5000L)
    }.await()
}

runBlocking{}块将阻止主线程直到其内部的代码完成,当我们进入runBlocking{}块时,我们发现您在async{}块上等待,因此我们必须等待async{}内部的内容完成,所以我们必须等待5秒钟。这就是为什么这段代码会阻止主线程。

2-但在第二种情况下:

runBlocking {
    async(CommonPool) {
        launch(CommonPool) {
            Thread.sleep(5000)
            runOnUiThread { textView.text = "Finish!" }
        }
    }.await()
}

你没有在launch{}块上等待(使用.join()),所以你在async{}块内唯一做的事情就是启动协程而不等待它完成。这就像async{}块是空的一样,因此async{}.await()runBlocking{}将不会等待任何东西完成。这就是为什么第二种情况不会阻塞主线程的原因。 我希望这回答了你的问题。


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