What is the difference between launch/join and async/await in Kotlin coroutines

55,828

Solution 1

  • launch is used to fire and forget coroutine. It is like starting a new thread. If the code inside the launch 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 of Deferred and you must use await on it. An uncaught exception inside the async code is stored inside the resulting Deferred 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 to be useful. I will quote the essential parts.

🦄 Coroutines

Essentially, coroutines are light-weight threads.

So you can think of a 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 coroutine, does something, and returns a token immediately as Job. You can call join on this Job to block until this launch coroutine 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. Read this for more details.

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

  1. 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

Launch Example

  1. 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 return Deferred<String> as String is return type of function
  • downloadTask2() would return Deferred<Int> as Int is return type of function
  • downloadTask3() would return Deferred<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

await example

Solution 4

  1. both coroutine builders namely launch and async are basically lambdas with receiver of type CoroutineScope which means their inner block is compiled as a suspend function, hence they both run in an asynchronous mode AND they both will execute their block sequentially.

  2. The difference between launch and async is that they enable two different possibilities. The launch builder returns a Job however the async function will return a Deferred object. You can use launch to execute a block that you don't expect any returned value from it i.e writing to a database or saving a file or processing something basically just called for its side effect. On the other hand async which return a Deferred as I stated previously returns a useful value from the execution of its block, an object that wraps your data, so you can use it for mainly its result but possibly for its side effect as well. N.B: you can strip the deferred and get its value using the function await, which will block the execution of your statements until a value is returned or an exceptions is thrown! You could achieve the same thing with launch by using the function join()

  3. both coroutine builder (launch and async) are cancelable.

  4. anything more?: yep with launch if an exception is thrown within its block, the coroutine is automatically canceled and the exceptions is delivered. On the other hand, if that happens with async the exception is not propagated further and should be caught/handled within the returned Deferred object.

  5. more on coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1

Solution 5

Async and Launch, both are used to create coroutines that run in the background. In almost every situation one can use either of them.

tl;dr version:

When you dont care about the task's return value, and just want to run it, you may use Launch. If you need the return type from the task/coroutine you should use async.

Alternate: However, I feel the above difference/approach is a consequence of thinking in terms of Java/one thread per request model. Coroutines are so inexpensive, that if you want to do something from the return value of some task/coroutine(lets say a service call) you are better off creating a new coroutine from that one. If you want a coroutine to wait for another coroutine to transfer some data, I would recommend using channels and not the return value from Deferred object. Using channels and creating as much number of coroutines as required, is the better way IMO

Detailed answer:

The only difference is in the return type and what functionality it provides.

Launch returns Job while Async returns Deferred. Interestingly enough, Deferred extends Job. Which implies it must be providing additional functionality on top of Job. Deferred is type parameterised over where T is the return type. Thus, Deferred object can return some response from the block of code executed by async method.

p.s. I only wrote this answer because I saw some factually incorrect answers on this question and wanted to clarify the concept for everyone. Also, while working on a pet project myself I faced similar problem because of previous Java background.

Share:
55,828

Related videos on Youtube

Roman  Elizarov
Author by

Roman Elizarov

Software Development Expert @ JetBrains, working on Kotlin, sports programming / ICPC, concurrency &amp; algorithms, math / quantitative finance; formerly @ Devexperts

Updated on March 04, 2022

Comments

  • Roman  Elizarov
    Roman Elizarov about 2 years

    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?

  • Faraaz
    Faraaz over 6 years
    is Async the right coroutine builder for network calls in Android?
  • Roman  Elizarov
    Roman Elizarov over 6 years
    The right coroutine builder depends on what you are trying to accomplish
  • Luis
    Luis over 6 years
    Can you elaborate on "You MUST NOT forget about the coroutine you've started with async"? Are there gotchas that one wouldn't expect for example?
  • Roman  Elizarov
    Roman Elizarov over 6 years
    "An uncaught exception inside the async code is stored inside the resulting Deferred and is not delivered anywhere else, it will get silently dropped unless processed."
  • Alexey Romanov
    Alexey Romanov over 6 years
    What exactly determines "usually printed to stderr in backend JVM applications and crashes Android applications"? Thread.getDefaultUncaughtExceptionHandler()?
  • Roman  Elizarov
    Roman Elizarov over 6 years
    This behavior can be overriden in various ways.
  • N. Kudryavtsev
    N. Kudryavtsev about 6 years
    So if I'd like to propagate an exception that may occur in some "fire and forget" code, should I use async<Unit> and write some boilerplate code that awaits its completion and checks if some exception was thrown?
  • Roman  Elizarov
    Roman Elizarov about 6 years
    You'd propagate exceptions by launching your "fire and forget" code with a parent coroutine or by installing your own CoroutineExceptionHandler in the context.
  • Pointer Null
    Pointer Null about 6 years
    I believe I can use async{} and forget it (not await for result). In such case it shall finish computation and be garbage-collected. Or not? Why you write "you must...", what happens if I don't (call await)?
  • Roman  Elizarov
    Roman Elizarov almost 6 years
    If you forget the result of async than it will finish and will be garbage collected. However, if it crashes due to some bug in your code, you'll never learn about that. That is why.
  • Boy
    Boy over 5 years
    if I run launch in a coroutine in Dispatcher.IO on Android, I wonder if it will swallow the exception or crash the Android app as you say...
  • Roman  Elizarov
    Roman Elizarov over 5 years
    Exception handling is unrelated to dispatcher that you use. You can learn more in the official docs on exception handling in coroutines: kotlinlang.org/docs/reference/coroutines/…
  • croc
    croc almost 5 years
    I still wouldn't say that "you must use await" on async, but rather that you should (because of the reasons @RomanElizarov said).
  • Akbolat SSS
    Akbolat SSS over 4 years
    Thanks for mentioning that launch is for sequential funs, while async for concurrent
  • alexanderktx
    alexanderktx about 4 years
    You have used launch once for all the tasks and the async for each one. Maybe it's faster because each one was launched in another coroutine and doesn't wait someone? This is an incorrect comparison. Usually performance is the same. One key difference is that launch always start a new coroutine instead of async that splits the owner one. One more factor is that if one of async tasks would fail for a reason the parent coroutine will fail either. That's why async isn't as popular as launch.
  • alexanderktx
    alexanderktx about 4 years
    Thanks for this comment. It collected all points of the thread. I'd add that not all launches are canceled e.g. Atomic cannot be canceled ever.
  • Thracian
    Thracian about 4 years
    This answer is not right, comparing async with suspend functions directly instead of launch. Instead of calling suspend function directly in example, if you call launch(Dispatchers.IO) {downloadTask1()} you will see that both are executed concurrently, not sequentially, you won't be able to get outputs but you will see that it's not sequential. Also if you do not concatenate deferred.await() and call deferred.await() separately you will see that async is sequential.
  • Salem
    Salem almost 4 years
    -1 this is just plain wrong. Both launch and async will start new coroutines. You're comparing a single coroutine with no children to a single coroutine with 3 children. You could replace each of the async invocations with launch and absolutely nothing would change with regard to concurrency.
  • truthadjustr
    truthadjustr almost 4 years
    The extraneous noise in this answer is adding complexity that is outside of the co-routine topic.
  • bio007
    bio007 about 3 years
    If you really want to compare it in this way you also need to launch all 3 download tasks in separate launch blocks. Otherwise you are mixing things up and confusing people. If you add val job1 = launch(Dispatchers.IO) { downloadTask1() }, val job2 = ... etc. Then you will really run it in parallel as you are suggesting with async.
  • coroutineDispatcher
    coroutineDispatcher about 3 years
    Probably one of the worst places to ask this question @RomanElizarov but why is join() not called awaitForCompletion() or something like that. We can agree that await() might be confusing. Just curious.
  • Roman  Elizarov
    Roman Elizarov about 3 years
    Historical reasons. It was a nice play on [an outdated] Thead-coroutine analogy. You can join a thread, you can join a coroutine.
  • Avilio
    Avilio about 3 years
    In the code block example for launch (used within runBlocking), I don't think you'd need "job.join()" since the runBlocking coroutine will wait for its children to complete. You'd only need this if you were creating a coroutine using a top-level scope, such as GlobalScope.
  • geiger
    geiger almost 3 years
    @Avilio it doesn't hurt, although sure, it doesn't make sense to call join() in this very example. Another thing: the launch example plainly won't compile (launch requires CoroutineScope).
  • jakchang
    jakchang almost 3 years
    this is wrong example. try launch{downloadTask1()} instead of downloadTask1() in launch test.
  • Farid
    Farid over 2 years
    "Async and Launch, both are used to create coroutines that run in the background" Coroutines doesn't necessarily mean execute in background unless you define it with Dispatchers. Your answer is making it even confusing for newbies
  • Alkis Mavridis
    Alkis Mavridis over 2 years
    Async can deliver a thrown Exception to the parent scope. Otherwise, how can one explain this? github.com/Kotlin/kotlinx.coroutines/issues/…
  • Alkis Mavridis
    Alkis Mavridis over 2 years
    This response does not tell the whole story (or I do not understand something correctly). In any case, here is a post about some weird behaviour of async: stackoverflow.com/questions/70890920/…