Pavel Bernshtam
Pavel Bernshtam

Reputation: 4444

Why Kotlin coroutines run in the same thread sequentially?

I thought that calling a "suspend" function from coroutine context using launch makes the call asynchronous. But in the example below I see that 2 invocations of placeOrder method are not running in the same thread one after another. What is my mistake?

import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File

fun main() = runBlocking {
    t("1")
    launch {
        t("2")
        placeOrder("C:\\Users")
        t("3")
    }
    launch {
        t("12")
        placeOrder("C:\\Program Files")
        t("13")
    }
    t("4")
}


fun t(s: String) {
    val currentThread = Thread.currentThread()
    println(s + ": " + currentThread.name + " " +     currentThread.id)
}

suspend fun placeOrder(d:String): String {
    t("placeOrder $d")
    val user = createUser(d) // asynchronous call to user service
    val order = createOrder(user) // asynchronous call to order service
    t("placeOrder $d finished")
    return order
}

suspend fun createUser(d:String): String {
    t("createUser $d")
    val toString = File(d).walk().map {
        it.length()
    }.sum().toString()
    t("createUser $d finished")
    return toString
}

suspend fun createOrder(user: String): String {
    t("createOrder $user")
    val toString = File("C:\\User").walk().map {
        it.length()
    }.sum().toString()
    t("createOrder $user finished")
    return toString
}

Output:

1: main 1
4: main 1
2: main 1
placeOrder C:\Users: main 1
createUser C:\Users: main 1
createUser C:\Users finished: main 1
createOrder 1094020270277: main 1
createOrder 1094020270277 finished: main 1
placeOrder C:\Users finished: main 1
3: main 1
12: main 1
placeOrder C:\Program Files: main 1
createUser C:\Program Files: main 1
createUser C:\Program Files finished: main 1
createOrder 5651227104: main 1
createOrder 5651227104 finished: main 1
placeOrder C:\Program Files finished: main 1
13: main 1

Upvotes: 6

Views: 6320

Answers (5)

Keivan Esbati
Keivan Esbati

Reputation: 3454

Essentially the code above is running synchronously even without runBlocking!

Since all the coroutines are launched into the main running on a single thread, ultimately you can use IO dispatcher which uses multiple threads.

Also, note that multiple coroutines can run on a single thread but they are never executed in parallel, they may appear as running in parallel because of thread switching from one coroutine to another when a new coroutine is launched or suspended.

Upvotes: 0

gor
gor

Reputation: 1056

replace launch with async

read this

Upvotes: 1

saurabh1489
saurabh1489

Reputation: 324

launch function signature:

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job (source)

As per the official Kotlin documentation link:

When launch { ... } is used without parameters, it inherits the context (and
thus dispatcher) from the CoroutineScope it is being launched from.

In your case, it inherits the context of the main runBlocking coroutine which runs in the main thread. As coroutine context includes a coroutine dispatcher that determines what thread or threads the corresponding coroutine uses for its execution, so you can provide a different CoroutineContext with your launch coroutine builder. For Example:

fun main() = runBlocking {
    t("1")
    launch(Dispatchers.Default) {
        t("2")
        placeOrder("C:\\Users")
        t("3")
    }
    launch(Dispatchers.Default) {
        t("12")
        placeOrder("C:\\Program Files")
        t("13")
    }
    t("4")
}

Regarding suspend functions, a suspending function is just a regular Kotlin function with an additional suspend modifier which indicates that the function can suspend the execution of a coroutine. It doesn't make the call asynchronous by default. You can execute your function code with custom dispatcher(e.g. IO dispatcher) using Kotlin's withContext() function as below:

suspend fun get(url: String) = withContext(Dispatchers.IO){/* Code for N/W logic */}

This will execute the body of the function in separate Thread than the calling coroutine context.

Here is 3-part series blog explaining usage of coroutines in Android apps : https://medium.com/androiddevelopers/coroutines-on-android-part-i-getting-the-background-3e0e54d20bb

Upvotes: 1

Marko Topolnik
Marko Topolnik

Reputation: 200148

Instead of writing suspendable IO, you wrote blocking IO:

File(d).walk().map {
    it.length()
}

Your functions never actually suspend and instead they block the single thread associated with their runBlocking dispatcher.

You gave your coroutines no opportunity to execute concurrently.

If you applied withContext(IO) { ... } around the above code, you'd get concurrency, but of the plain-old Java type, several threads being blocked in IO operations together.

Upvotes: 4

Kiskae
Kiskae

Reputation: 25573

The reason for this behavior is twofold:

  1. All your coroutines are executed in the runBlocking scope, which is a single-threaded event loop. So this means only a single thread is ever used unless a different context is specified. (launch(Dispatchers.IO) as an example)
  2. Even then it would be possible for the coroutines to interleave, except your coroutines do call suspending functions which actually have to suspend. This means it is effectively a normal sequential function call. If your functions included a yield() or delay(..) call you would see the coroutines interleave in execution.

Upvotes: 3

Related Questions