为什么这个协程阻止UI线程?

时间:2018-02-07 12:06:31

标签: android multithreading kotlin kotlin-coroutines

我发现我的Android应用中的这个特定代码阻止了UI线程:

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

textView.text = "Finish!"

我一直在使用协同程序执行多项任务,并且它们永远不会阻止UI线程,因为可以在the documentation中阅读:

  

。协同程序提供了一种避免阻塞线程并用更便宜和更可控的操作替换它的方法:暂停协程

但奇怪的是,这段代码:

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

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

表现得像预期的那样;不阻止,等待五秒然后打印结果(我需要更新UI后,只有在sleep完成后才能更新)

文档说明asynclaunch可以单独使用,不需要合并。事实上,async(CommonPool)应该足够了。

那么这里到底发生了什么?为什么它仅适用于async+launch

更新

我的完整示例代码:

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()
            }
        }
    }
}

2 个答案:

答案 0 :(得分:6)

runBlocking不是在UI线程上启动协同程序的方法,因为正如其名称所示,它将阻止托管线程,直到协程完成。您必须在launch上下文中UI,然后切换到重量级操作的CommonPool上下文。而且,虽然我们处于此状态,但您应放弃async-await对并使用withContext

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

withContext将暂停协程,直到完成,然后在父上下文中恢复它,即UI

关于使用CommonPool

尽管使用CommonPool非常简单,但这意味着您要提交到所有依赖项都可以使用的全局池,而您无法控制它。我建议的是创建自己的线程池,还可以根据需要配置它。代码非常简单:

val threadPool = Executors.newCachedThreadPool().asCoroutineDispatcher()

然后你只需说withContext(threadPool)而不是withContext(CommonPool)

答案 1 :(得分:1)

正如documentation告诉我们的那样:

  

[runBlocking]运行新的协程,阻止当前线程,直到完成为止。不应该从协程中使用此函数。它旨在将常规阻止代码桥接到以挂起样式编写的库,以便在main函数和测试中使用。

正如引用的那样,它经常用于与常规协同程序一起使用的测试中,也用于main方法以等待协同程序的完成。

此外,此tutorial将有助于了解其用例。