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

groostav

08/08/2019, 9:36 PM
hmm. I'm curious about the
Job
and parent-child'ing.
Copy code
suspend fun doLongRunningWork {
  val myContext = coroutineContext
  requireNotNull(myContext[Job])

  val async1 = someScope.async { 
    //...
  }
  val async2 = someScope.async(myContext) {
    //...
  }

  //maybe this code joins on async1 & async2, maybe not. should that impact my approach?
}
the latter exerts a rather strange back-pressure on the callers coroutine-entrypoint. Why the distinction?
z

zak.taccardi

08/08/2019, 9:37 PM
what do you mean it joins?
it’s async
so the
suspend fun doLongRunningWork()
will complete (resume) potentially before the two async coroutines complete
you would need to add
awaitAll(async1, async2)
to suspend
doLongRunningWork()
until they complete
g

groostav

08/08/2019, 9:38 PM
I know. My question is more about the purpose of
myContext
What I want is a rationale for why you might want to launch a child async process in your context and why, idiomatically (by default) you don't.
I just found the docs for
coroutineScope {}
builder...
I guess I'm just confounded by the case where a child-job completes and a grand-child (or further descendant) job is still running, keeping your
runBlocking
from completing... although, does
async
complete in such a case? does
await
give you a value but
join()
suspend? I must write tests.
ahhhh this is the
Completing
state.
z

zak.taccardi

08/08/2019, 9:53 PM
I guess I don’t understand
you are asking why you may want to launch a coroutine that launches 2 async tasks? The answer is parallelism
oh - your point is why you would launch it in a different scope?
launch async in a different scope?
g

groostav

08/08/2019, 9:56 PM
yeah so the key point is
async1
is not a child of the Job executing
doLongRunningWork
and
async2
is a child of same. Why do we need to distinguish between the two?
well the former will let the job executing
doLongRunningWork
complete when its own block is complete. The latter will prevent the
Job
of
doLongRunningWork
from completing, pushing it into the
Completing
state, for as long as
async2
is running.
z

zak.taccardi

08/08/2019, 9:58 PM
I guess I’m not following why you would want to do this though
g

gildor

08/09/2019, 2:47 AM
I also don't understand your question. This code is strange by itself and you may have different behavior depending on where this doLongRunningWork will be called, because you just may break structured concurrency easily if myContext is not belongs to someScope, because you will replace Job from someScope I just not sure what exactly you want to achieve
I see no reason for such structured code to exist, you always never should launch coroutines to different scope from suspend function, it's just a side effect, it may be needed sometimes (launch one more background task. What is semantics what you trying to achieve
g

groostav

08/09/2019, 4:46 AM
Its not trying to accomplish something I'm trying to understand the ramifications. As you say,
you [never should] launch coroutines to different scope from suspending function
in short, what im asking is: why? Consider your the designer of "TornadoUI" UI framework. Lets say you use an annotation processing system and you want to support coroutines. Should you idiomatically ask callers to write functions like
Copy code
@Tornado suspend fun onClick(e: ActionEvent)
or should you ask them to write
Copy code
@Tornado fun CoroutineScope.onClick(e: ActionEvent)
--or something else? If you do decide that "simplicity is most important", and so you want your users to write regular UI event handlers
@Tornado suspend fun handle(e: Event)
, what should you do if they call
launch(coroutineContext) { delay(9999)
. Do you refuse to dispatch further events to that handler? Should you throw an exception?
I guess I'm looking for a general interpretation of back-pressure that doesnt exist.
l

louiscad

08/09/2019, 6:00 AM
Fixed your code:
Copy code
suspend fun doLongRunningWork = coroutineScope {
  val async1 = async { 
    //...
  }
  val async2 = async {
    //...
  }
  ... // Probably await the async values, otherwise, use launch.
} // This code definitely joins async1 & async2 if you don't await them
g

groostav

08/09/2019, 6:32 PM
@louiscad what are your suggestions for the UI framework?
g

gildor

08/10/2019, 1:01 AM
Example above with onClick may have sanse in some cases (like suspend on click can be useful in some cases), but if you want integration with coroutines I would expose those events as Flow
what should you do if they call
launch(coroutineContext) { delay(9999)
This is indifferent to launch and scopes, user can just call delay(9999) And now, you shouldn't throw exception I don't see any problem in this case if you use proper event stream abstraction like Flow, which has different strategies for backpressure. With suspend function you always have only 2 choices: suspend until client is processing or launch new coroutine and suspend, as you mentioned above, with Flow you can also buffer and conflate
l

louiscad

08/11/2019, 2:12 PM
@groostav What does the
@Tornado
annotation means and does?
1
g

groostav

08/11/2019, 11:24 PM
Its a binding annotation for your UI framework. Like a swing action listener or an fxml click listener or an android tap listener. You could just as easily expose an
operator fun EntryPointAPI.plusAssign(handler: your_concurrent_handler_type)
if you wanted a winforms style ui API. The thing I’m trying to get at is: what should the lambda type be? And what should the UI framework do if it returns but silently appended a child job to the context you gave it?
l

louiscad

08/11/2019, 11:37 PM
Here's the extension I use on Android (agnostic to binding if any) to wait for clicks in coroutines: https://github.com/LouisCAD/Splitties/blob/e4c940736b00154a828d74c7cc10252686432685/modules/views-coroutines/src/androidMain/kotlin/splitties/views/coroutines/VisibilityAndClicks.kt#L35-L51 The scope of these coroutines is bound to the lifecycle of the UI of course.
7 Views