Vampire
04/18/2025, 3:16 PMGlobalScope.launch { /* do the background task */ }
is the appropriate way.
I'm just a bit uncertain as I have to opt-in to DelicateCoroutinesApi
and the AI-Reviewer suggests to use a CoroutineScope
instead.
It more details are interesting,
when a request for X is done, X is taken from cache or calculated and put to cache.
After X was requested, it is quite likely, that Y and Z are requested next,
so I want to trigger asynchronously in the background that Y and Z are calculated and put to the cache.
But this should not delay the returning of X.PHondogo
04/18/2025, 3:45 PMrkechols
04/18/2025, 3:46 PMCoroutineScope
that it uses to launch coroutines without blocking to wait for the result.CLOVIS
04/18/2025, 3:46 PMCoroutineScope
as a class attribute of the Cache
class and launch
into that. That way, no one has to wait for it, but a user can still kill the cache and be sure there's no work left somewhere else. That will particularly be useful for testing, where you can ensure everything started by the cache is killed before the test terminates.Vampire
04/18/2025, 7:18 PMprivate val prefetchScope = CoroutineScope(...)
to the file and then use prefetchScope
instead of GlobalScope
?
And if so, what would ...
be?
<http://Dispatchers.IO|Dispatchers.IO>
?
(Sorry, I'm not that used to working with coroutines at that level yet.)Vampire
04/18/2025, 7:21 PMrkechols
04/18/2025, 7:36 PMCoroutineScope
over GlobalScope
is lifecycle management / cancelability. For example in mobile apps you don't care about coroutines that are fetching data for a certain view once the user navigates away from the view, so those coroutines should be canceled at that point.
Looks like your context is a ktor web server? If the cache is long-lived along with the whole server process, then you may not have any need to cancel the pre-fetch coroutine(s), in which case global scope would be functionally equivalent. But It'd probably still be better practice to create a non-global coroutine scope wrapped in a Cache
class that has your prefetchBindingArtifacts
function declared as a method. Encapsulation like that should help with unit tests and such.Vampire
04/18/2025, 7:50 PM<http://Dispatchers.IO|Dispatchers.IO>
.rkechols
04/18/2025, 8:00 PMbindingsCache
works. If I understand correctly, the IO dispatcher is only relevant if you're calling non-suspend functions that you know primarily wait on IO rather than doing CPU work.Vampire
04/18/2025, 9:59 PMrkechols
04/18/2025, 11:18 PMActionCoords.buildVersionArtifacts
and I see that it is not a suspend function. This means that your dispatcher decision does simply depend on whether that function does more IO-bound work (loading files from disk, network requests, etc.) or CPU-bound work (transforming data, computing checksums, etc.)Vampire
04/21/2025, 9:24 AMDmitry Khalanskiy [JB]
04/22/2025, 8:04 AMGlobalScope
is that it's more difficult to control what happens if the coroutines started in it throw an exception: you have to do launch(CoroutineExceptionHandler { _, e -> /* something */ })
each time you launch a coroutine. With a custom scope, you can write CoroutineScope(CoroutineExceptionHandler { _, e -> /* something */ })
once and define the strategy for all its coroutines.
Also, if one coroutine fails, the other coroutines running in the same scope get cancelled. To prevent that, use CoroutineScope(SupervisorJob() + CoroutineExceptionHandler { _, e -> /* something */ })
. GlobalScope
uses a SupervisorJob()
, too.