If I pass in the coroutine scope from the caller, ...
# coroutines
r
If I pass in the coroutine scope from the caller, it works fine, but it feels silly to add a parameter for that.
l
Use
CoroutineScope(coroutineContext).apply { … }
then
r
That worked (though I used
run
instead of
apply
). Can you explain why?
g
Without parameter you don't have proper child/parent relationship and do not achive structured concurrency safety
If you just need old semantics, use GlobalScope instead creating new one
And manage lifecycle of this Flowable manually
r
No, I wanted to migrate this to structured concurrency. The docs of
coroutineScope
indicates:
The provided scope inherits its [coroutineContext] CoroutineScope.coroutineContext] from the outer scope
I'm just trying to understand why it does not in this case?
g
coroutineScope suspends until child coroutines are running
r
Why would that be an issue here?
g
Isn't rxFlowable creates a new coroutine?
r
rxFlowable
in kotlinx-coroutines-rx2 1.0.0 is an extension of
CoroutineScope
g
Exactly
This is coroutine builder that starts new coroutine
And coroutineScope builder do not allow return until all the child coroutines are finished, so this function just doesn't fit to such function as your
r
Ok... so then the
coroutineScope
parent should suspend until the
rxFlowable
coroutine is done, shouldn't it? That's exactly what I want...
I must be missing something fundamental here
g
But this is not how your old code works then
r
Ah, right, the old code returns the rxFlowable, but it is not yet started until it is passed to the consumer.
g
Exactly
r
Ok, so by creating a new coroutine scope, we don't suspend waiting for the
rxFlowable
to start, which it never will because we're suspended... Tricky.
g
I'm not very familiar with rx builders for coroutines. I will check later what exactly going on there and what is the best solution in this case
r
Thanks @gildor... appreciate that. I think this definitely deserves some documentation.
g
coroutineScope has documentation about this semantics
But if you add nested rx builder there it becomes more complicated
r
Exactly -- I meant more on the rx builder side.
Docs for
CoroutineScope(...)
don't really explain why one would use that instead of
coroutineScope { ... }
either.
g
I believe that CoroutineScope is not the best choice
It just creates new, unmanaged coroutine scope
But because you pass coroutineContext to constructor with Job this scope will be cancelled on parent cancel
r
So its sort of a hack to connect to the parent scope? But there might be a better approach?
g
But I think the right and more explicit way: your function should has coroutine scope as param, to explicitly say:this is async operation and it must be attached to some scope
And this is client responsibility to choose scope for it depending on semantics: current scope, parent scope or global one
r
That was working for sure, it just felt wrong to pass in scope. But your reasoning about being able to choose the right scope makes sense.
Normally this would be an extension function on
CoroutineScope
so it would automatically get the parent scope. Its just that it is already extending something else so in this case I wasn't able to do that.
g
It's not even hack to attaching scope, because you attaching to Job, which is old coroutines semantics without structured concurrency: implicitly attach to parent Job
r
Ok, so that's not great. Ideally I'd have a way to attach to the scope the suspending function is running in. If I can't do that, I'll pass in the scope.
g
Yes, we don't have multiple receivers for extension functions, so in this case everything looks a bit strange, I usually move receiver as argument and use CoroutineSvope as new receiver for consistency with other builders
👍 1
Rule of thumb is that suspend function cannot create coroutines that doesn't finish their work when function is returned
👍 2
You still can achieve this with GlobalScope and just attaching parent Job, but this exactly what old semantics of coroutines did, a this worked and you can write safe code with it of course, it's just not so safe as structured concurrency which forces you to have parent to any async operation and be explicit about this
r
Yes, I like the explicitness/safety
Converting it to use CoroutineScope as receiver also got rid of all the ugly `this@`'s as well
One related question. Why does
CoroutineScope.rxFlowable
default its context to
EmptyCoroutineContext
instead of the context of the scope receiver?
g
This is how scope builders work: empty context just means "use context of scope", but allows you override context if you want
👍 2
@rocketraman One more question, why does your function is marked as
suspend
? It doesn’t look that you have any suspend functrions call inside, also if you migrated to CoroutineScope as receiver it’s now safe and possible to call this function from standard function
And one more point why this code is more tricky. It’s because Rx doesn’t have structure concurrency and it’s valid to create new flowable without attaching to lifycycle or some parent disposable and lifecycle management is responsibility of subscriber. Just to copy that behaviour is enough to use
GlobalScope.rxFlowable
which creates global flowable, so will be exactly how Rx works. Solution with CoroutineScope is little bit different because explicitly attaches created flowable’s lifecycle to coroutine scope, so rx now managed by the same scope and requires scope to be created, so it’s becoming Structured concurrency for Rx
r
You're right,
suspend
isn't needed any more... it was when I was using creating a CoroutineScope with the
coroutineContext
of the suspend function.