Is there a premade way to launch a job in a corout...
# coroutines
m
Is there a premade way to launch a job in a coroutine scope/suspend function that wont cause that scope/function to wait for it to finish? I have a need to launch a heartbeat task thats just an infinite loop sending a periodic signal. Right now im just using
launch
then at the end of my function cancelling the heartbeat job so the function doesnt sit forever for the heartbeat to finish. Is there a way to launch a job in a way that autocancels itself if its the only thing a scope is waiting for?
k
It kind of depends on the structure of your program. You could heap allocate a coroutine scope and supply it via DI and then just cancel that scope when your program terminates. If you have something simpler with a
main
function then you can do something like this:
Copy code
fun main() = runBlocking {
  val heartbeatJob = launch { heartbeat() }
  programEntryPoint()
  heartbeatJob.cancelAndJoin()
}

suspend fun programEntryPoint() { ... }

suspend fun heartbeat() { ... }
But to more directly answer your question — no, there’s not any easy or good way to launch and forget a job. It breaks structured concurrency.
GlobalScope
exists, but should mostly be avoided.
m
makes sense, your example is basically what im doing as well, was just curious if there was some coroutine lifecycle event or like a "weak reference" equivalent so the heartbeatJob would still be under structured concurrency but the parent scope wouldnt wait on it to finish
👍 1
c
> Is there a way to launch a job in a way that autocancels itself if its the only thing a scope is waiting for?
Copy code
val mainJob = Job()

GlobalScope.launch {
    // Heartbeat code
    while (true) …
}.also { mainJob.invokeOnCompletion { it.cancel("Main job finished, no need for heartbeat anymore") } }
However the hearbeat doesn't access the coroutine context of the main job with this approach
k
What benefit does that solution have over maintaining an entirely separate coroutine scope that’s provided via DI?
m
@PHondogo thats basically exactly what i was looking for. background scope on tests is sweet ill have to remember that. I need the functionality non test code so i guess manually joining is my only option for now but ill keep an eye on that github issue
👍 1
k
You could pretty easily write a helper function that offers a background scope like runTest!
m
yea im gonna dig into the implementation of background scope to port over the functionality, thanks for the replies yall!
p
For my cases i've made this function:
Copy code
suspend fun<T> runWithDaemons(
        daemons: suspend CoroutineScope.() -> Unit,
        main: suspend CoroutineScope.() -> T
    ) : T {
        return coroutineScope {
            val daemonsJob = launch {
                coroutineScope(block = daemons)
            }
            val result = coroutineScope(block = main)
            daemonsJob.cancel()
            result
        }
    }
👍 2
Usage example:
Copy code
runWithDaemons(
            daemons = {
                launch { // launching new daemon job
                    // ... 
                }
                delay(50000L)
            }
        ) {
            launch { // launching new main job
                // ... 
            }
            delay(1000)
        }
k
If you wanted an API surface without two lambdas, something like this could be possible:
Copy code
suspend fun <T> withDaemonScope(block: suspend CoroutineScope.(CoroutineScope) -> T): T {
  return coroutineScope {
    val daemonScope = CoroutineScope(Job(coroutineContext[Job]!!))
    val ret = coroutineScope { block(daemonScope) }
    daemonScope.cancel()
    ret
  }
}
Usage would look like the following:
Copy code
suspend fun foo() = withDaemonScope { daemonScope -> 
  daemonScope.launch { heartbeat() }
  doOtherWork()
}
👍 1