Chunsen
Chunsen

Reputation: 13

Kotlin async function not run in parallel

I have a kotlin function just as following, And I expected it can wrap a sync IO action into async.

suspend fun <T> runIOAsync(f:suspend () -> T): Deferred<T> = coroutineScope{
    async(Dispatchers.IO) {
       f()
    }
}

Then I have my code in the calling side like

 runBlocking {
            repeat(5) {
                runIOAsync {
                    println(it)
                    println(Thread.currentThread())
                    Thread.sleep(3000)
                    println("After sleep $it")
                }.await()
            }
        }

But the actual out put is

0
Thread[DefaultDispatcher-worker-1 @coroutine#2,5,main]
After sleep 0
1
Thread[DefaultDispatcher-worker-1 @coroutine#3,5,main]
After sleep 1
2
Thread[DefaultDispatcher-worker-1 @coroutine#4,5,main]
After sleep 2
3
Thread[DefaultDispatcher-worker-1 @coroutine#5,5,main]
After sleep 3
4
Thread[DefaultDispatcher-worker-1 @coroutine#6,5,main]
After sleep 4

Which seems all tasks from my function are executed serially. Any one can please help to give an explanation

Upvotes: 1

Views: 1604

Answers (1)

Joffrey
Joffrey

Reputation: 37650

Let's put aside runIOAsync for the moment.

You're using await() right after calling async(), which means that you're effectively waiting for the end of your async execution before executing the next one.

Instead, start all tasks first and then await all of them. For instance you can use awaitAll:

runBlocking {
    List(5) {
        async(Dispatchers.IO) {
            println(it)
            println(Thread.currentThread())
            Thread.sleep(3000)
            println("After sleep $it")
        }
    }.awaitAll()
}

Also, the way you're encapsulating the scope in runIOAsync is wrong, you will be waiting for the end of the async execution even without calling await() (coroutineScope is a suspending function that waits for all its child coroutines before resuming).

Instead, use coroutineScope to define the boundary of your coroutines executions, and you don't even have to await them. Since you don't need to get values from this code, you can also use launch instead of async here:

coroutineScope {
    repeat(5) {
        launch(Dispatchers.IO) {
            println(it)
            println(Thread.currentThread())
            Thread.sleep(3000)
            println("After sleep $it")
        }
    }
}

Declaring a suspend function returning a Deferred should be a red flag, and is quite confusing from an API standpoint: if you suspend it means you wait, if you return Deferred it means you don't wait (you immediately return a handle to some running computation). A function that does both would be quite weird.

If what you want is to make suspending code from IO-bound code, you can use instead the existing withContext function:

// this suspends until the code inside is done
withContext(Dispatchers.IO) {
    // run some blocking IO code
}

However, note that this is independent from defining concurrent code. If you want to run multiple things concurrently, even with suspend functions, you'll need coroutine builders such as async or launch like in the above code.

Upvotes: 6

Related Questions