https://kotlinlang.org logo
Title
t

Tom Everton

04/13/2022, 11:38 AM
Hey folks 👋, I’m relatively new to coroutines I have a question about coroutine scopes which I can’t seem to find a consistent answer for. Any input is appreciated! Say I have in an application I have two classes, an outer which will live for the whole lifecycle of the application and an inner class which is only alive for part of the lifecycle. For the outer class I create a coroutine scope which will be cancelled if the class is closed:
class Outer : Closeable {
    private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    private var inner: Inner? = null

    fun startInner() {
        inner = Inner(InnerRepo(), InnerApi())
    }

    fun stopInner() {
        inner.close()
        inner = null
    }

    override fun close() {
        scope.cancel()
    }
    ... // More code that uses the outer scope
}
And in the inner I have some code that calls suspend functions:
class Inner(
    private val repo: InnerRepo,
    private val api: InnerApi
) : Closeable {
    private val innerScope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO> + SupervisorJob())    

    fun sendData() {
        innerScope.launch {
            api.sendData(repo.getData())
        }
    }

    override fun close() {
        innerScope.cancel()
    }
}
Is there a way to link the
innerScope
to the
outerScope
as it makes sense that the
innerScope
would always want to be cancelled if the outer scope cancels, or is that even something that should be done? Also, if the pattern above is completely wrong let me know! Cheers
s

Sam

04/13/2022, 12:07 PM
I would recommend that in
startInner
you call
scope.launch { ... }
, something like this:
fun startInnner() {
    innerJob = scope.launch { Inner(this, InnerRepo(), InnerApi()) }
}
Where I added
this
as the first argument to
Inner
, that would be a reference to the
CoroutineScope
created by
launch
, so that
Inner
wouldn't need to create its own scope.
e

ephemient

04/13/2022, 12:12 PM
if you can't stick to scoping like that for some other reasons, you can at least
innerJob = Job(outerScope.coroutineContext.job)
t

Tom Everton

04/13/2022, 12:41 PM
Thank you both, good suggestions, that makes sense. Any idea if this sort of thing is standard practice?
s

Sam

04/13/2022, 1:35 PM
Seems pretty normal to me 👍. Using
launch
is the straightforward way to achieve structured concurrency, where one job/scope is a child of another. Capturing that inner scope in an object is probably not uncommon if your code is more object-oriented and the work done by the child job is non-trivial.
I have found that trying to use structured concurrency with objects rather than functions can sometimes feel like fighting against the design of the coroutines API 😞
t

Tom Everton

04/13/2022, 1:42 PM
Great, thanks for the help Sam. Yeah, it does seem suited to a more functional style but luckily we’re shifting the codebase in that direction so it should start to integrate better. I’m just trying to get the fundamentals right as I’m switching from RxJava but I don’t want to just try and fit everything into the old Rx style but instead start thing in terms of structured concurrency