I've got some code that is basically a hierarchy o...
# coroutines
b
I've got some code that is basically a hierarchy of sub-tasks that I think would be a good fit to migrate to coroutines, but I've got some dumb questions trying to get a few things straight. 1. Is the proper way to propagate a failure up the chain (from, say any child task in that hierarchy) to define a set of custom exceptions to encode different events and use those? If so, is an exception typically used even for a non-error "finished" case? (one of the sub-tasks can finish on its own for a non-error reason, so I could define something like a "FinishedSuccessfully" exception for that? Or is there a better pattern to use there?) 2. If object A has a coroutinescope defined and creates object B--which will launch its own child coroutines--and I want object B's coroutines to be launched within object A's coroutinescope, is the best way to just have object A pass its scope to object B in the ctor (or in function calls)? Or is there a better way to (implicitly?) have B 'inherit' A's coroutine scope?
e
1. Exceptions should never be used to represent a “success” value, especially since if a coroutine throws an exception in a CoroutineScope it will cancel sibling coroutines as well as itself. You should go either with Roman E.’s strategy of using sealed class hierarchies or result types to represent success/failure for everything except I/O issues or define a set of exceptions you want to throw on failures and be very strict about handling those exception types. 2. Yes, if Object B’s coroutine scope’s lifetime should be tied to that of Object A, then Object A should pass its coroutine scope to Object B to be used by it. If you want Object B to be able to be constructed on its own, you can set a coroutine context as a default parameter to Object B’s constructor to be used when none is passed.
b
Thanks, Evan! I'll check out that article.
In my case, I actually want the successful conclusion of one child to cancel everything else--so maybe it's appropriate there?
I don't really like the idea of a non-error exception--I agree it doesn't feel quite right--but I wasn't sure how else to take advantage of the natural propagation. That article seems to refer to using smarter return values, but in this case the completion will be asynchronous to the initial method call, so a return value won't work.
(I've also found that using
coroutineScope { ... }
automatically inherits the calling scope, so looks like that would be an option for #1)
e
If that’s the case you could report the status of your jobs via a channel. The first one to complete can send its computation result over the channel and whatever launched the jobs can cancel all the other coroutines as necessary
b
Yeah, I was wondering about that, too. I have a similar mechanism in place now, but I was thinking I'd be able to get rid of a fair bit of code if the exception model worked/was approprriate.
Is there a way to select on all child jobs of a coroutine? I.e. so I could detect if any of them finished and, if so, shut everything down?
e
Child jobs of a coroutine can be retrieved via the current coroutine’s job - https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/children.html
If you want to manually cancel the children you can just to
job.children.forEach { it.cancel() }
b
What if I want to detect if any one child in the whole tree has completed?
From what I've seen the mechanisms seem to be orchestrated around all child jobs completing, not any, so that might be hard.
e
That’s what the channel would be good for. You can pass the channel along as deep as you want and listen to it. When a child job reports it’s successful you can cancel all child jobs at the top of your concurrency tree and it will percolate downward
b
Ok, so this would be one channel created by the parent that got passed as a
SendChannel
to all children tasks, basically?
e
Yep!
b
Ok, thanks Evan...I'll play around with that.
e
Good luck!
b
👍