Hey folks :wave:, I’m relatively new to coroutines...
# coroutines
t
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:
Copy code
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:
Copy code
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
I would recommend that in
startInner
you call
scope.launch { ... }
, something like this:
Copy code
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
if you can't stick to scoping like that for some other reasons, you can at least
innerJob = Job(outerScope.coroutineContext.job)
t
Thank you both, good suggestions, that makes sense. Any idea if this sort of thing is standard practice?
s
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
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