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

8fsztsew  于 2022-11-25  发布在  Kotlin
关注(0)|答案(3)|浏览(237)

弃用警报

此代码使用旧的Coroutines Api。如果您使用的是kotlinx-coroutines 1.1.0或更高版本,此代码对您没有任何用处

  • 最初的问题是 *

我发现我的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()
}

行为符合预期;不阻塞,等待5秒,然后打印结果(我需要在sleep完成后更新UI)
文档中说asynclaunch可以单独使用,不需要组合使用,实际上async(CommonPool)就足够了。
那么到底是怎么回事呢?为什么它只对async+launch有效?

更新(2021)

[Deprecation alert]此代码使用旧的Coroutines 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()
            }
        }
    }
}
m1m5dgzv

m1m5dgzv1#

注意:这篇文章可以追溯到协同程序的预发布版本。我更新了调度程序的名称以匹配发布版本。

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

1zmg4dgp

1zmg4dgp2#

正如documentation告诉我们的:
[ runBlocking ]运行新协程并以可中断方式阻塞当前线程直到其完成此函数不应从协程使用它被设计为将常规阻塞代码桥接到以挂起样式编写库,用于main函数和测试
正如所引用的,它经常被用在使用常规协程的测试中,也被用在main方法中,以等待协程完成。
此外,本教程还将帮助您了解它的用例。

mklgxw1f

mklgxw1f3#

我很抱歉这么晚才给你答复,但我希望你会觉得这对你有帮助。
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{}不会等待任何事情完成。这就是为什么第二种情况不会阻塞主线程。我希望这能回答这个问题。

相关问题