Colton Idle
05/12/2023, 4:04 PMstreetsofboston
05/12/2023, 4:06 PM// inside `suspend` fun
...
coroutineScope { ... here goes your launch/async ... }
...
Colton Idle
05/12/2023, 4:08 PMyschimke
05/12/2023, 4:12 PMFrancesc
05/12/2023, 4:29 PMcoroutineScope
will wait for the coroutines inside to complete before returning, which is correct.
You should not "fire and forget" from a suspend
function because that breaks convention, a suspend
function should only return once it has completed its work; if it launches a coroutine and returns, then it hasn't completed its work before returningCasey Brooks
05/12/2023, 4:30 PMcoroutineScope { }
block will wait for the jobs launched inside it to complete before returning, so it’s not perfectly suitable for a “fire-and-forget” scenario if you need the main coroutine to continue executing beyond that block. If you want to truly fire-and-forget, you should create a new CoroutineScope
and launch into that (making sure to hold onto it somewhere that you can cancel at the appropriate point in your app’s lifecycle).
This is actually an example of Structured Concurrency in action. The Coroutines APIs force you to be aware of where you’re launching jobs so they’re tracked properly. You must decide whether to use coroutineScope { }
, which ties those jobs to the current coroutine, or launch them onto a separate CoroutineScope
object and make it that scope’s job to handle cancellation and errors.Francesc
05/12/2023, 4:31 PMCoroutineScope
or pass the scope as a parameter (and, in both cases, the function is not suspending)Casey Brooks
05/12/2023, 4:35 PMclass MyRepository {
suspend fun doSomeWork() {
delay(1000) // do some initial work
// don't do this, you'll get stuck in the coroutineScope block longer than you want
coroutineScope {
launch { /* some other work*/ }
launch { /* some other work*/ }
}
// continue with other work
}
}
class MyRepository {
private val coroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
public fun close() {
coroutineScope.cancel()
}
suspend fun doSomeWork() {
delay(1000) // do some initial work
// do this instead. By referencing another CoroutineScope, it's clear these jobs are not managed by doSomeWork()
coroutineScope.launch { /* some other work*/ }
coroutineScope.launch { /* some other work*/ }
// continue with other work
}
}
Francesc
05/12/2023, 4:38 PMIf you need to launch a coroutine that keeps running after your function returns, then make your function an extension ofhttps://elizarov.medium.com/coroutine-context-and-scope-c8b255d59055or passCoroutineScope
as parameter to make your intent clear in your function signature. Do not make these functions suspending:scope: CoroutineScope
Suspending functions, on the other hand, are designed to be non-blocking and should not have side-effects of launching any concurrent work. Suspending functions can and should wait for all their work to complete before returning to the caller³.
Casey Brooks
05/12/2023, 4:47 PMCoroutineScope
, but you need to define what what scope is. suspend fun
itself does not have a CoroutineScope
receiver to launch into, so you need to find a way to access it. The two options are either opening a coroutineScope { }
block and suspending until its children complete, or launching onto some other scope.
Note that in the above article, it’s referening the runBlocking
function, and like launch { }
, and async { }
the lambda has a CoroutineScope
receiver because they are considered “top-level coroutine builders”. You’re supposed to just launch other jobs inside those builders.
But a suspend
function is running inside one of those builders, and is not a top-level coroutine itself. It’s supposed to suspend until it is completely finished with everything it needs. so if you want to do something like launch fire-and-forget tasks you need to do it in relation to the scope it’s currently running in. You’re not supposed to have jobs still running on the parent’s coroutineScope once the suspend
function returns, which is why you need to move them to another coroutineScope if you want to fire-and-forgetFrancesc
05/12/2023, 4:48 PMsuspend
function has returnedCasey Brooks
05/12/2023, 4:57 PMFrancesc
05/12/2023, 6:27 PMfun myFunction() {
// some stuff here
// other stuff there
scope.lauch { channel.send(someValue) }
}
than to have a suspend
function that launches coroutines that continue to run after the function returns, as that breaks the convention that a suspend
function should not return until all work is completePatrick Steiger
05/13/2023, 3:03 AMfun CoroutineScope.someAsyncFun()
or
suspend fun someFun()
Francesc
05/13/2023, 3:38 PMfun CoroutineScope.someWorkAsync()
fun someWorkAsync(scope: CoroutineScope)
suspend fun someWorkThatCompletsWhenWeReturn()
Patrick Steiger
05/13/2023, 3:39 PMFrancesc
05/13/2023, 3:40 PMsuspend
functions instead, I think the code is easier to read and understand that way; I use the extension function or passing a scope
in limited casesChris Fillmore
05/13/2023, 4:11 PM