https://kotlinlang.org logo
#coroutines
Title
# coroutines
r

rocketraman

11/12/2018, 10:33 PM
If I pass in the coroutine scope from the caller, it works fine, but it feels silly to add a parameter for that.
l

louiscad

11/12/2018, 11:02 PM
Use
CoroutineScope(coroutineContext).apply { … }
then
r

rocketraman

11/12/2018, 11:07 PM
That worked (though I used
run
instead of
apply
). Can you explain why?
g

gildor

11/12/2018, 11:13 PM
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

rocketraman

11/12/2018, 11:16 PM
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

gildor

11/12/2018, 11:19 PM
coroutineScope suspends until child coroutines are running
r

rocketraman

11/12/2018, 11:20 PM
Why would that be an issue here?
g

gildor

11/12/2018, 11:20 PM
Isn't rxFlowable creates a new coroutine?
r

rocketraman

11/12/2018, 11:21 PM
rxFlowable
in kotlinx-coroutines-rx2 1.0.0 is an extension of
CoroutineScope
g

gildor

11/12/2018, 11:21 PM
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

rocketraman

11/12/2018, 11:23 PM
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

gildor

11/12/2018, 11:24 PM
But this is not how your old code works then
r

rocketraman

11/12/2018, 11:25 PM
Ah, right, the old code returns the rxFlowable, but it is not yet started until it is passed to the consumer.
g

gildor

11/12/2018, 11:26 PM
Exactly
r

rocketraman

11/12/2018, 11:27 PM
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

gildor

11/12/2018, 11:27 PM
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

rocketraman

11/12/2018, 11:28 PM
Thanks @gildor... appreciate that. I think this definitely deserves some documentation.
g

gildor

11/12/2018, 11:29 PM
coroutineScope has documentation about this semantics
But if you add nested rx builder there it becomes more complicated
r

rocketraman

11/12/2018, 11:30 PM
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

gildor

11/12/2018, 11:32 PM
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

rocketraman

11/12/2018, 11:33 PM
So its sort of a hack to connect to the parent scope? But there might be a better approach?
g

gildor

11/12/2018, 11:34 PM
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

rocketraman

11/12/2018, 11:35 PM
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

gildor

11/12/2018, 11:36 PM
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

rocketraman

11/12/2018, 11:37 PM
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

gildor

11/12/2018, 11:38 PM
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

rocketraman

11/12/2018, 11:41 PM
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

gildor

11/13/2018, 12:41 AM
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

rocketraman

11/13/2018, 7:00 AM
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.
4 Views