chrisChris
chrisChris

Reputation: 99

How to understand and improve async-await in coroutines?

I would like to improve my async-await in coroutines. This is my solution

val coroutineContext: CoroutineContext
        get() = job + Dispatchers.IO
[...]
            lifecycleScope.launch(coroutineContext) {
                async {
                    client = viewModel.getClientItem(clientId)
                }.await()
                if (inEditMode != null) {
                    changedEditMode = true
                }
[...]

@Marko Topolnik wrote in Why does this coroutine block UI Thread?

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.

Why doing it with Main Dispatchers and then withContext using Default dispatchers is betters solution than async. It will also block main thread and work the same what is the difference. And how to handle this approaches?

Greetings

EDIT: My other solution is this below or withContext(Dispatchers.IO)

            lifecycleScope.launch(Dispatchers.Main) {
                withContext(Dispatchers.Default) {
                    client = viewModel.getItem(clientId)
                }
[...]
}

Now as I read I do not use runBlocking and don't block main thread? In that configuration it works the same as with async-await. In your opinin that's better solution ?

Upvotes: 2

Views: 1037

Answers (1)

Joffrey
Joffrey

Reputation: 37849

Why doing it with Main Dispatchers and then withContext using Default dispatchers is betters solution than async. It will also block main thread and work the same what is the difference

withContext doesn't block, it suspends. The terminology might should similar but the behaviour is very different. While Thread.sleep is executing on the Default dispatcher (and thus on one of this dispatcher's threads), the main thread can keep running other code. The coroutine launched via launch(Main) here is in a suspended state, which means the main dispatcher knows it can execute other things instead. The linked answer here explains why runBlocking was a bad idea, because no matter what you launch in it runBlocking has to block the current thread while it's executing coroutines inside.

Now back to your code. Note that async { doStuff() }.await() makes little sense. It is equivalent to simply doStuff().

Another thing is that you're passing a job explicitly to launch via the context here. This doesn't seem right, and could lead to problems. If you want to use Dispatchers.IO, you don't need to pass a job too.

That said, even Dispatchers.IO seems like a stretch here. You should instead make viewModel.getClientItem a suspend function (if not already), and leave the responsibility of using the right dispatcher to this function. Maybe you don't even need IO at all here, or maybe it's an HTTP or DB call that already has its own thread pool anyway.

So I would replace your whole code with:

lifecycleScope.launch {
    client = viewModel.getClientItem(clientId)
    if (inEditMode != null) {
        hangedEditMode = true
    }
}

Upvotes: 2

Related Questions