Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin Coroutines Async Await Sequence

Can you please explain me what is the difference between these two blocks of code. First time prints 421, but second prints 606. Why first one is parallel and second one is sequential?

fun main(args: Array<String>) = runBlocking {

    var time = measureTimeMillis {
        val one = async { one() }
        val two = async { two() }
        val int1 = one.await()
        val int2 = two.await()
        println(int1 + int2)

    }

    println(time)


    time = measureTimeMillis {
        val one = async { one() }.await()
        val two = async { two() }.await()
        println(one + two)

    }

    print(time)
}

suspend fun one(): Int {
    delay(200)
    return 12
}

suspend fun two(): Int {
    delay(400)
    return 23
}
like image 393
toffor Avatar asked Sep 17 '18 13:09

toffor


People also ask

Are coroutines sequential?

by default the methods are executed sequentially one after another. N.B:- codes are sequentially executed within a coroutine by default.

How do you wait for coroutine to finish Kotlin?

We can wait for the coroutine to finish by calling join() on the Job. For example, suppose we have a suspend function to download some files. We can launch this coroutine and capture the resulting job, which we can later use to join — to wait for the operation to complete.

Are Kotlin coroutines asynchronous?

Note: The AsyncTask class was deprecated in Android 11. Kotlin coroutines are now the recommended solution for async code.


Video Answer


3 Answers

val one = async { one() }
val two = async { two() }
val int1 = one.await()
val int2 = two.await()

What this does:

  1. spawn task one
  2. spawn task two
  3. await on task one
  4. await on task two

val one = async { one() }.await()
val two = async { two() }.await()

What this does:

  1. spawn task one
  2. await on task one
  3. spawn task two
  4. await on task two

There's no concurrency here, it's purely sequential code. In fact, for sequential execution you shouldn't even use async. The proper idiom is

val one = withContext(Dispatchers.Default) { one() }
val two = withContext(Dispatchers.Default) { two() }
like image 179
Marko Topolnik Avatar answered Oct 23 '22 18:10

Marko Topolnik


In the first variant you get a Deferred<Int> for both async-calls. As the documentation of Deferred shows so nicely there are several states the deferred object might be in. From the outside that state is now either new or active but surely not completed yet. On your second variant however the first async-await needs a completed state already otherwise you could not have any value there. However on your async{one()}.await() the second async is not known yet. Note also that the return value of await() is now Int and not Deferred anymore, so the coroutine must have been executed by then. Check also the documentation of await().

In other words:

val one = async { one() }
val two = async { two() }

Both one and two are now Deferred<Int>. None has been called yet (or might have been called yet). As soon as you call one.await() it may start already both one and two, just because it has the resources for it (even if you didn't use two.await() anywhere in your code).

On the second variant however:

val one = async { one() }.await()
val two = async { two() }.await()

Even though it creates a coroutine for async {one()} it must set a value to one immediately, because you are calling await() on it. The types of one and two are both Int. So as soon as the first of those lines is hit, the async code needs to be executed immediately. By then nobody knows that another asynchronous call has to be executed as we wait for the value of the first. If the first wouldn't have an await, the coroutine would again be executed in parallel, e.g.:

val one = async { one() }
val two = async { two() }.await()

will execute one() and two() in parallel.

So maybe this can be summarized to: only those coroutines can be executed in parallel on an await, that are known/spawned by then.

like image 33
Roland Avatar answered Oct 23 '22 19:10

Roland


The thumb-rules:

  • Use withContext when you do not need the parallel execution.
  • Use async only when you need the parallel execution. Both withContext and async can be used to get the result which is not possible with the launch.
  • Use withContext to return the result of a single task.'
  • Use async for results from multiple tasks that run in parallel.

Check this for more details

like image 35
MohamedHarmoush Avatar answered Oct 23 '22 19:10

MohamedHarmoush