How to launch a Kotlin coroutine in a `suspend fun` that uses the current parent Scope?

Viewed 9698

How can I launch a coroutine from a suspend function and have it use the current Scope? (so that the Scope doesn't end until the launched coroutine also ends)

I'd like to write something like the following –

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    go()
}

suspend fun go() {
    launch {
        println("go!")
    }
}

But this has a syntax error: "Unresolved Reference: launch". It seems launch must be run in one of the following ways –

GlobalScope.launch {
    println("Go!")
}

Or

runBlocking {
    launch {
        println("Go!")
    }
}

Or

withContext(Dispatchers.Default) {
    launch {
        println("Go!")
    }
}

Or

coroutineScope {
    launch {
        println("Go!")
    }
}

None of these alternatives does what I need. Either the code "blocks" instead of "spawning", or it spawns but the parent scope won't wait for its completion before the parent scope itself ends.

I need it to "spawn" (launch) in the current parent coroutine scope, and that parent scope should wait for the spawned coroutine to finish before it ends itself.

I expected that a simple launch inside a suspend fun would be valid and use its parent scope.

I'm using Kotlin 1.3 and cotlinx-coroutines-core:1.0.1.

3 Answers

You should make the function go an extension function of CoroutineScope:

fun main() = runBlocking {
    go()
    go()
    go()
    println("End")
}

fun CoroutineScope.go() = launch {
    println("go!")
}

Read this article to understand why it is not a good idea to start in a suspend functions other coroutines without creating a new coroutineScope{}.

The convention is: In a suspend functions call other suspend functions and create a new CoroutineScope, if you need to start parallel coroutines. The result is, that the coroutine will only return, when all newly started coroutines have finished (structured concurrency).

On the other side, if you need to start new coroutines without knowing the scope, You create an extensions function of CoroutineScope, which itself it not suspendable. Now the caller can decide which scope should be used.

I believe I found a solution, which is with(CoroutineScope(coroutineContext). The following example illustrates this –

import kotlinx.coroutines.*

fun main() = runBlocking {
    go()
    go()
    go()
    println("End")
}

suspend fun go() {
//  GlobalScope.launch {                     // spawns, but doesn't use parent scope
//  runBlocking {                            // blocks
//  withContext(Dispatchers.Default) {       // blocks
//  coroutineScope {                         // blocks
    with(CoroutineScope(coroutineContext)) { // spawns and uses parent scope!
        launch {
            delay(2000L)
            println("Go!")
        }
    }
}

However, Rene posted a much better solution above.

Say you are dealing with some RxJava Observable and it isn't the time to refactor them, you can now get a hold of a suspend function's CoroutineScope this way:

suspend fun yourExtraordinarySuspendFunction() = coroutineScope {
    val innerScope = this // i.e. coroutineScope
    legacyRxJavaUggh.subscribe { somePayloadFromRxJava ->
        innerScope.launch {
            // TODO your extraordinary work
        }
    }
}
Related