What is the difference between launch/join and async/await in Kotlin coroutines
In the kotlinx.coroutines
library you can start new coroutine using either launch
(with join
) or async
(with await
). What is the difference between them?
Solution 1:
launch
is used to fire and forget coroutine. It is like starting a new thread. If the code inside thelaunch
terminates with exception, then it is treated like uncaught exception in a thread -- usually printed to stderr in backend JVM applications and crashes Android applications.join
is used to wait for completion of the launched coroutine and it does not propagate its exception. However, a crashed child coroutine cancels its parent with the corresponding exception, too.async
is used to start a coroutine that computes some result. The result is represented by an instance ofDeferred
and you must useawait
on it. An uncaught exception inside theasync
code is stored inside the resultingDeferred
and is not delivered anywhere else, it will get silently dropped unless processed. You MUST NOT forget about the coroutine you’ve started with async.
Solution 2:
I find this guide https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md to be useful. I will quote the essential parts
🦄 coroutine
Essentially, coroutines are light-weight threads.
So you can think of coroutine as something that manages thread in a very efficient way.
🐤 launch
fun main(args: Array<String>) {
launch { // launch new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
So launch
starts a background thread, does something, and returns a token immediately as Job
. You can call join
on this Job
to block until this launch
thread completes
fun main(args: Array<String>) = runBlocking<Unit> {
val job = launch { // launch new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // wait until child coroutine completes
}
🦆 async
Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines. The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred -- a light-weight non-blocking future that represents a promise to provide a result later.
So async
starts a background thread, does something, and returns a token immediately as Deferred
.
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.
So Deferred
is actually a Job
. See https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html
interface Deferred<out T> : Job (source)
🦋 async is eager by default
There is a laziness option to async using an optional start parameter with a value of CoroutineStart.LAZY. It starts coroutine only when its result is needed by some await or if a start function is invoked.
Solution 3:
launch
and async
are used to start new coroutines. But, they execute them in different manner.
I would like to show very basic example which will help you understand difference very easily
- launch
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnCount.setOnClickListener {
pgBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch {
val currentMillis = System.currentTimeMillis()
val retVal1 = downloadTask1()
val retVal2 = downloadTask2()
val retVal3 = downloadTask3()
Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1}, ${retVal2}, ${retVal3} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
pgBar.visibility = View.GONE
}
}
// Task 1 will take 5 seconds to complete download
private suspend fun downloadTask1() : String {
kotlinx.coroutines.delay(5000);
return "Complete";
}
// Task 1 will take 8 seconds to complete download
private suspend fun downloadTask2() : Int {
kotlinx.coroutines.delay(8000);
return 100;
}
// Task 1 will take 5 seconds to complete download
private suspend fun downloadTask3() : Float {
kotlinx.coroutines.delay(5000);
return 4.0f;
}
}
In this example, my code is downloading 3 data on click of btnCount
button and showing pgBar
progress bar until all download gets completed. There are 3 suspend
functions downloadTask1()
, downloadTask2()
and downloadTask3()
which downloads data. To simulate it, I've used delay()
in these functions. These functions waits for 5 seconds
, 8 seconds
and 5 seconds
respectively.
As we've used launch
for starting these suspend functions, launch
will execute them sequentially (one-by-one). This means that, downloadTask2()
would start after downloadTask1()
gets completed and downloadTask3()
would start only after downloadTask2()
gets completed.
As in output screenshot Toast
, total execution time to complete all 3 downloads would lead to 5 seconds + 8 seconds + 5 seconds = 18 seconds with launch
- async
As we saw that launch
makes execution sequentially
for all 3 tasks. The time to complete all tasks was 18 seconds
.
If those tasks are independent and if they do not need other task's computation result, we can make them run concurrently
. They would start at same time and run concurrently in background. This can be done with async
.
async
returns an instance of Deffered<T>
type, where T
is type of data our suspend function returns. For example,
-
downloadTask1()
would returnDeferred<String>
as String is return type of function -
downloadTask2()
would returnDeferred<Int>
as Int is return type of function -
downloadTask3()
would returnDeferred<Float>
as Float is return type of function
We can use the return object from async
of type Deferred<T>
to get the returned value in T
type. That can be done with await()
call. Check below code for example
btnCount.setOnClickListener {
pgBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch {
val currentMillis = System.currentTimeMillis()
val retVal1 = async(Dispatchers.IO) { downloadTask1() }
val retVal2 = async(Dispatchers.IO) { downloadTask2() }
val retVal3 = async(Dispatchers.IO) { downloadTask3() }
Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1.await()}, ${retVal2.await()}, ${retVal3.await()} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
pgBar.visibility = View.GONE
}
This way, we've launched all 3 tasks concurrently. So, my total execution time to complete would be only 8 seconds
which is time for downloadTask2()
as it is largest of all of 3 tasks. You can see this in following screenshot in Toast message