Why CoroutineScope contains coroutineContext when any suspending function can get it from Continuation

Many functions from kotlinx-coroutines-core need CoroutineScope receiver which has coroutineContext property.

My question is why when any suspending function can obtain CoroutineContext by calling

suspendCoroutine<CoroutineContext> {
    it.resume(it.context)
}

My concern is that some functions like launch use CorutineScope and others like withContext don’t use it.

So if I define

suspend fun CoroutineScope.myFunction()

and use both launch and withContext in its body I’m potentially operating on two different CoroutineContexts and I may get pretty surprising behavior.

You are correct, defining a function like this:

suspend fun CoroutineScope.myFunction()

can lead to breaking structured concurrency and introduce a whole set of bugs.
The solution to this seems to be to not declare a function that takes a CoroutineScope as suspend, whatever requires suspending could be done in the coroutine scope instead.

1 Like

But not having suspend seems quite limiting. Eg. I cannot use async + await pattern like this

fun CoroutineScope.myFunction() {
    val d1 = async { /* */ } // async needs CoroutineScope
    val d2 = async { /* */ }

    d1.await() // await is suspending <-- COMPILATION ERROR
    d2.await()
}
1 Like

For that use case, make the function suspend and wrap the entire function body with:

suspend fun myFunction() = coroutineScope {
    val d1 = async { /* */ } // async needs CoroutineScope
    val d2 = async { /* */ }

    d1.await()
    d2.await()
}

Note, this method will create a child scope that inherits all the properties of the scope that called myFunction(). Also the function would not return until both d1 and d2 have finished, regardless if you await/join them!

2 Likes

Yeah, that makes sense.

So what’s the purpose of CoroutineScope receiver? Or when I’m supposed to use it?

1 Like

See Coroutine Context and Scope.

CoroutineScope is specifically for introducing concurrency.

Every suspend method really should finish all its work before it returns.

Methods that don’t finish can take a CoroutineScope receiver or parameter to hook it into structured concurrency and return a Job/Deferred as a handle to that concurrent work.

1 Like

Oh, I see.

Thank you very much!

BTW another relevant question to my topic is

1 Like