Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this coroutine block UI Thread?

Deprecation alert

This code uses the old Coroutines Api. If you're using kotlinx-coroutines 1.1.0 or newer, this code won't be useful to you

The original question was:

I'm finding that this particular code in my Android App blocks the UI Thread:

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

textView.text = "Finish!"

I've been using coroutines for several tasks, and they never block UI Thread, as can be read in the documentation:

. Coroutines provide a way to avoid blocking a thread and replace it with a cheaper and more controllable operation: suspension of a coroutine

But curiously, this code:

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

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

behaves as expected; does not block, waits five seconds then prints the result (I need to update the UI after, and only after the sleep is completed)

The documentation says that async and launch can be used independently and don't need to be combined. In fact, async(CommonPool) should be enough.

So what's really going on here? why does it work only with async+launch ?

Update (2021)

[Deprecation alert] This code uses the old Coroutines Api. If you're using kotlinx-coroutines 1.1.0 or newer, forget about this code

My full sample code:

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()
            }
        }
    }
}
like image 788
voghDev Avatar asked Feb 07 '18 12:02

voghDev


Video Answer


1 Answers

Note: this post dates back to the pre-release version of coroutines. I updated the names of dispatchers to match the release version.

runBlocking is not the way to start a coroutine on the UI thread because, as its name says, it will block the hosting thread until the coroutine is done. You have to launch it in the Main context and then switch to the Default context for the heavyweight operation. You should also drop the async-await pair and use withContext:

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

withContext will suspend the coroutine until done and then resume it in the parent context, which is Main.

like image 198
Marko Topolnik Avatar answered Sep 24 '22 03:09

Marko Topolnik