-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fencing pre-request fetches #4313
Comments
For a completed The complexity of Instead of using suspend fun CoroutineScope.joinChildren() {
coroutineContext.job.children.forEach { it.join() }
ensureActive() // don't forget!
} So my tip is: suspend fun foo(u: UserId, b: BarId) = coroutineScope {
val user = async { userService.load(u) }
val bar = async { barService.load(b) }
joinChildren()
doFoo(user.getCompleted(), bar.getCompleted())
}
Another idea?I sometimes used: suspend fun foo(u: UserId, b: BarId) = coroutineScope {
val userDeferred = async { userService.load(u) }
val barDeferred = async { barService.load(b) }
val user = userDeferred.await()
val bar = barDeferred.await()
doFoo(user, bar)
}
|
Thanks for the comment, @fvasco, I do agree that
Nope,
I'm not sure what you mean. If the write operation requires a value, then how can it be invoked without the value being available? If some processes need to happen before other processes, but the two processes don't exchange data, what I would do would be to create phantom data (" |
val read1 = async { … }
val read2 = async { … }
val read3 = async { … }
write1(read1.await(), read2.await())
write2(read1.await(), read3.await()) In this situation, I'm not saying the KotlinX.Coroutines library behaves incorrectly in this situation—I'm saying that the code above is buggy because |
suspend fun CoroutineScope.joinChildren() {
for (c in coroutineContext.job.children) c.join()
// ensureActive() // don't forget!
}
fun main() = runBlocking<Unit>(Dispatchers.Default) {
val a = async { "a" }
val b = async<String> { error("b missing") }
joinChildren()
println("coroutineContext.isActive = ${coroutineContext.isActive}")
println("a = " + a.getCompleted())
println("b = " + b.getCompleted())
} often fails with
|
Use case
A very common pattern I see very often in codebases is having a bunch of independent read-only operations that are pre-requisites to one write operation. Since the read-only operations are independent, we want them to execute concurrently. However, we don't want to execute the write operations if the read-only operations fail.
Without coroutines, this is typically written:
Of course, in the real world, we see examples with more than 10 of such prior read-only operations.
The goal of this issue is to find a canonical way of implementing this pattern with coroutines. The following are a few patterns I considered.
coroutineScope {}
Mentally, launching the read-only operations in a specific
coroutineScope {}
seems to be exactly what I'm searching for: they all execute concurrently before the write operation can start, and the Coroutines library takes care of cancellation and exceptions. However, sincecoroutineScope {}
introduces a lexical scope, it isn't possible to easily extract the data into local variables, and tricks are necessary.This works, but is particularly not fun to write, and the necessity to declare variables as
var
is not great.joinAll
Another option is to start a bunch of jobs and use
joinAll
followed bygetCompleted
to access the values:To me, using
getCompleted()
is application code is a code smell, and in fact it is common to forget to add one of the deferred to thejoinAll
code, leading to runtime errors.await
A variation of the previous example, using
await()
instead ofgetCompleted()
to avoid the need forjoinAll()
:This is definitely the easiest version to read and write, but it has a few downsides:
.await()
'ing the same value many times. Looking at the code, it seems.await()
spin-locks, so probably not a great idea?Arrow parZip
Using Arrow Fx Coroutines, we can use
parZip
:I guess this is exactly what I'm asking for, but it doesn't really feel like idiomatic Kotlin code. In particular, the usage of non-trailing lambdas, the declaration of the names in a different place than their contents, and the added indentation in the primary function code.
Another idea?
I don't really feel satisfied with any of the above options (though I have seen them all in production code), but I can't really come up with a satisfying design either. My best idea is something like:
where
prerequisites()
creates aCoroutineScope
similar to the one created bycoroutineScope {}
, which is joined by the call ofclose()
, and the delegates use.getCompleted()
under the hood.Still, this solution will compile even if
fence.close()
isn't called, breaking at runtime. Also, this solution breaks at run-time if any of the read operations attempts to use the results of another read operation (which only the.await()
example above can handle).What do you think? Is there another pattern I missed? Could this be improved somehow?
The text was updated successfully, but these errors were encountered: