If a suspend fun can only be called within another suspend fun

If a suspend fun can only be called within another suepend fun, how is the first suspend fun ever called actually?
How is the launch function able to kick start a suspend function? I am a little confused, any insights would be greatly appreciated!

1 Like

This depends on whether you mean syntactically call or actually call (in the runtime environment).

Assuming you mean the first, you do this with opening a coroutine scope with the runBlocking method.
According to the documentation, you usually do this on the very top level of you application: Coroutines basics | Kotlin
I recommend this as a primer for coroutines, so you get familiar with the most basic concepts.

From said tutorial:

fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
}
3 Likes

It just is :wink: Sorry, but explaining this would require a good understanding of coroutines internals. So let’s just say runBlocking(), launch() and async() are coroutine builders or initializers - they can initialize a coroutine and run a suspend lambda using it.

2 Likes

Thanks for the explanation.
I am wondering how functions like runBlocking works
To me, it just seems logically impossible to ever start a suspend function since a suspend function must be called within another suspend function. I am kind interested in the inplementation of runBlocking and launch
Thanks for your insight!

I am rather interested in the internals of it
Thanks for the reply!

It is not entirely impossible to call a suspend function from a non-suspend one. It is just not directly possible. We have to initialize some things for a coroutine to work: start/acquire an event looper, create a coroutine context and its job, schedule suspendable code to the looper, etc. This is what functions like runBlocking() and launch() do. And suspend function can invoke another suspend function directly.

1 Like

Fyi The source code of runBlocking is publicly available here kotlinx.coroutines/Builders.kt at master · Kotlin/kotlinx.coroutines · GitHub

A quick Google should return some in-depth articles on how coroutines work.

You can of course make your own non-suspending function that starts a coroutine.
Back in 2017 (which was a very different API for coroutines), I implemented async and await for JS promises
NOTE: This code is not great or complete. It was for me experimenting and learning coroutines.

fun <T> async(block: suspend () -> T): Promise<T> = Promise { resolve, reject ->
    block.startCoroutine(object : Continuation<T> {
        override val context = EmptyCoroutineContext
        override fun resumeWithException(exception: Throwable) {
            reject(exception)
        }

        override fun resume(value: T) {
            resolve(value)
        }
    })
}

suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
    then(
            onFulfilled = { cont.resume(it) },
            onRejected = { cont.resumeWithException(it) }
    )
}

EDIT:
Here’s a short setup for some coroutine suspension. Nothing fancy, it isn’t the same as “runBlocking”, and I haven’t looked at it for correctness other than updating the APIs. Hopefully it’s an interesting thing to play around with (you can edit right in the forum):

import kotlin.coroutines.Continuation
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.coroutines.suspendCoroutine

fun main() {
    println("Hello World!")
    runSuspending {
        doSuspend()
        doSuspend()
        doSuspend()
    }
    println("Done!")
}

fun runSuspending(block: suspend () -> Unit) {
    block.startCoroutine(object : Continuation<Unit> {
        override val context = EmptyCoroutineContext
        override fun resumeWith(result: Result<Unit>) {
            println("Resumed")
        }
    })
}

suspend fun doSuspend() {
    suspendCoroutine<Unit> { cont ->
    	println("Suspend")

        // Try commenting out this line to see some different behavior.
        cont.resumeWith(Result.success(Unit))
    }
}
1 Like

Ohh, interesting. Is this method of starting a coroutine (block.startCoroutine()) still considered a valid way? It is not marked as deprecated. How does it know how to schedule the coroutine - there is no context, so no dispatcher as well.

Also note that runSuspending() in your example schedules the block asynchronously, so your example is pretty misleading. “Done!” runs concurrently to the code inside runSuspending(), so it could happen before “Suspend”.

Good catch, yes. I wanted to get an editable starting point that compiled so there are probably more errors. If anyone has time to update it for correctness, feel free :slight_smile:

2 Likes