Hello, in a project we needed to replicate a funct...
# coroutines
m
Hello, in a project we needed to replicate a functioning block of coroutine code we had in similar functions (the one inside
also { }
), to send an event before calling a service. We came up with this, but I think it’s ugly and was wondering if it could be simplified:
Copy code
override suspend fun validateCart(request: ValidateCartRequest): ValidateCartResponse {
        supervisorScope {
            launch(Dispatchers.Unconfined) {
                domainEventsPublisher.publishCartValidationSubmitted(request.cartId)
            }
        }
        return super.validateCart(request).also { response ->
            supervisorScope {
                launch(Dispatchers.Unconfined) {
                    if (response.isValid) domainEventsPublisher.publishCartValidationSucceeded(response)
                }
            }
        }
    }
can i wrap everything in a single supervisorScope? Would it be equivalent to this code?
s
What’s the reason you are using the
supervisorScope
? Unless I’m missing something,
Copy code
supervisorScope { launch { doThing() } }
is pretty much equivalent to
Copy code
doThing()
(aside from error handling, which you can do with try/catch)
m
we wanted to “fire&forget” those events asynchronously, without waiting for their completion and prevent the errors from bubbling up in the caller thread, so after some discussion in this channel some time ago I got confirmation that supervisorScope was appropriate for this case
how can you say it’s pretty much equivalent? If i just wrote
Copy code
publish()
super.validateCart()
they would be executed sequentially, in a blocking way
s
In fact
supervisorScope
will always wait for its child jobs to complete. So a supervisor scope with a single child job is very similar in behaviour to no supervisor scope at all.
Typically in coroutines there are two patterns: •
suspend
functions run some work and return when it’s complete •
CoroutineScope
extension functions launch background work and return immediately The library deliberately makes it very difficult for you to make a
suspend
function that launches background work and returns before it’s complete.
m
hmm I see, wasn’t aware of this aspect. So how would be the correct way to launch background work and carry on with the computation?
moreover, we thought that supervisor scope would prevent any exceptions raised inside from affecting the parent thread, and actually they did: we saw that in dev environment, upon auth/connection errors with the event queue, the main app was shielded, so this is what we needed. But still, you say that those scopes are blocking anyway waiting for completion? So is this code useless anyway in that sense?
s
Yes,
supervisorScope
does stop exceptions from propagating, but if that’s all you need it for, it’s much simpler to use try/catch
Based on your code example, for your background work problem, I would suggest: • Introduce a
Channel
in the
domainEventsPublisher
• Have the
domainEventsPublisher
launch its own long-running coroutine that reads messages from the channel and uses them to publish events • Then all the
validateCart
function needs to do is send a message to the channel; it doesn’t need to wait for the
domainEventsPublisher
to handle the message. (Actually this new coroutine + channel could live anywhere, they don’t necessarily have to be inside the
domainEventsPublisher
)
If you have the option to modify the
validateCart
function signature, a different option would be to add a
CoroutineScope
as a parameter/receiver and use that when you need to launch background tasks, but that’s not such a clean design IMO
m
Thanks for the useful insights! We still have very little knowledge of coroutines, so this helps a lot. I’ll discuss the changes with the team, thanks again.