9

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

3 Answers 3

10

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.

Sign up to request clarification or add additional context in comments.

Comments

1

As documentation tells us:

[runBlocking] runs new coroutine and blocks current thread interruptibly until its completion. This function should not be used from coroutine. It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in main functions and in tests.

As quoted, it’s often used in tests that work with regular coroutines and also in main methods to wait for the completion of coroutines.

Also, this tutorial will help understanding its use cases.

Comments

0

I'm sorry for this late answer, but I hope you find it helpful.

1- Because in the first situation

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

the runBlocking{} block will block the main thread until the code inside it finishes, then when we go inside the runBlocking{} block we find that you awaited on the async{} block, so we must wait until what's inside async{} finishes so we must wait for the 5 seconds. That why this code blocks the main thread.

2- But in the second situation:-

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

You didn't await on the launch{} block (via using .join()), so you the only thing you did inside the async{} block is launching the coroutine without waiting for it to finish. It is like that the async{} block is empty, so the async{}.await() and runBlocking{} will not wait for anything to finish. That's why this second situation doesn't block the main thread. I hope this answers it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.