WithContext, Async or Launch inside Android coroutine — Kotlin

Canato
5 min readNov 26, 2019

Often you have to perform multiple operations. Kotlin gives you some mechanisms to decide how you want to run those operations.

For this cases we have 3 options withContext, launch and async. And each one have a behaviour depending of what you wanna achieve.

In the end of the text there is a resume.

WithContext

fun testWithContext {
var resultOne = "Hardstyle"
var resultTwo = "Minions"
Log.i("withContext", "Before")
resultOne = withContext(Dispatchers.IO) { function1() }
resultTwo = withContext(Dispatchers.IO) { function2() }
Log.i("withContext", "After")
val resultText = resultOne + resultTwo
Log.i("withContext", resultText)
}
suspend fun function1(): String {
delay(1000L)
val message = "function1"
Log.i("withContext", message)
return message
}
suspend fun function2(): String {
delay(100L)
val message = "function2"
Log.i("withContext", message)
return message
}

Function 2 executes faster than 1. But `withContext` is a suspend call, so it won't go to the next line until it finished.
But the good point is that your UI thread is free to run while the IO thread executes `function1` and `function2`.

When you run, the log result would be:

withContext: Before
withContext: function1
withContext: function2
withContext: After
withContext: function1function2

When to use: when you need to use the answer from the method called, `withContext` will wait for it result and don't block the main thread.

Launch

fun testLaunch {
var resultOne = "Hardstyle"
var resultTwo = "Minions"
Log.i("Launch", "Before")
launch(Dispatchers.IO) {resultOne = function1() }
launch(Dispatchers.IO) {resultTwo = function2() }
Log.i("Launch", "After")
val resultText = resultOne + resultTwo
Log.i("Launch", resultText)
}
suspend fun function1(): String {
delay(1000L)
val message = "function1"
Log.i("Launch", message)
return message
}
suspend fun function2(): String {
delay(100L)
val message = "function2"
Log.i("Launch", message)
return message
}

Using launch will not block your main thread, but in other hand the execution of this part of the code will not wait for the launch result since launch is not a suspend call.

When you run, the log result would be:

Launch: Before
Launch: After
Launch: HardstyleMinions //don't wait for results
Launch: function2 // 2 first
Launch: function1

When to use: If you don't need the result from the method called (for example it just send a update or change a color or tracker some information without return)

Async

fun testAsync {
Log.i("Async", "Before")
val resultOne = Async(Dispatchers.IO) { function1() }
val resultTwo = Async(Dispatchers.IO) { function2() }
Log.i("Async", "After")
val resultText = resultOne.await() + resultTwo.await()
Log.i("Async", resultText)
}
suspend fun function1(): String {
delay(1000L)
val message = "function1"
Log.i("Async", message)
return message
}
suspend fun function2(): String {
delay(100L)
val message = "function2"
Log.i("Async", message)
return message
}

If you wanna use async result you will need to add .await() to your variable and this would cause a block in your main thread.
What I mean: If you are running this on a UI thread and call async for result it will stop the execution at the line `val resultText …` and any click would get response or trigger anything (annoying).

When you run, the log result would be:

Async: Before
Async: After
Async: function2 //2 first
Async: function1
Async: function1function2 //wait for results and block.

When to use: When making two or more network call in parallel, but you need to wait for the answers before computing the output.

Obs: if you use async but don't wait for result it will work like launch

Megazord

Sample putting all together so we can see the run order.

var resultOneCont = "Hardstyle"
var resultTwoCont = "Minions"
Log.i("withContext", "Before")
resultOneCont = withContext(Dispatchers.IO) { function1("withContext") }
resultTwoCont = withContext(Dispatchers.IO) { function2("withContext") }
Log.i("withContext", "After")
val resultTextCont = resultOneCont + resultTwoCont
Log.i("withContext", resultTextCont)

var resultOneLau = "Hardstyle"
var resultTwoLau = "Minions"
Log.i("Launch", "Before")
launch(Dispatchers.IO) { resultOneLau = function1("Launch") }
launch(Dispatchers.IO) { resultTwoLau = function2("Launch") }
Log.i("Launch", "After")
var resultTextLau = resultOneLau + resultTwoLau
Log.i("Launch", resultTextLau)

Log.i("asyncNoResult", "Before")
async(Dispatchers.IO) {function1("asyncNoResult")}
async(Dispatchers.IO) {function2("asyncNoResult")}
Log.i("asyncNoResult", "After")

Log.i("Async", "Before")
val resultOne = async(Dispatchers.IO) { function1("Async") }
val resultTwo = async(Dispatchers.IO) { function2("Async") }
Log.i("Async", "After")
val resultText = resultOne.await() + resultTwo.await()
Log.i("Async", resultText)

//repeated at the end to show new value
resultTextLau = resultOneLau + resultTwoLau
Log.i("Launch", resultTextLau)

Changes: `function1` and `function2` now receive a string

suspend fun function1(s: String): String {
delay(1000L)
val message = "function1"
Log.i(s, message)
return message
}

suspend fun function2(s: String): String {
delay(100L)
val message = "function2"
Log.i(s, message)
return message
}

Result

I/withContext: Before
I/withContext: function1
I/withContext: function2
I/withContext: After
I/withContext: function1function2
I/Launch: Before
I/Launch: After
I/Launch: HardstyleMinions
I/asyncNoResult: Before
I/asyncNoResult: After
I/Async: Before
I/Async: After
//function2 exec
I/Launch: function2
I/asyncNoResult: function2 //asyncNoResult
I/Async: function2
//function1 exec
I/Launch: function1
I/asyncNoResult: function1 //asyncNoResult
I/Async: function1
//Results
I/Async: function1function2
I/Launch: function1function2

Resume:

  • withContext:
    Use to get a result from another thread,
    Don't block the thread where is called, block the code,
    Run in Sequence;
  • launch / async no result:
    Use when don't need result,
    Don't block the code where is called,
    Run in parallel;
  • async for result:
    When you need to wait the result and can run in parallel for efficiency,
    Block the code where is called,
    run in parallel;

Extra: One nice different on Launch and Async:
Launch re-throw exceptions, Async hold exception until await() is called

--

--

Canato

Android Developer, RPG players, DJ and producer and ex- many stuffs, let's have a coffee