Shouldn't ```suspend fun someFun(flow: Flow<A>): U...
# coroutines
r
Shouldn't
Copy code
suspend fun someFun(flow: Flow<A>): Unit {
  withContext(Dispatchers.Default) {
    launch {
      flow.collect { }
    }
  }
}
return immediately? So I did
Copy code
suspend fun someFun(flow: Flow<A>): Unit {
  println("0")
  withContext(Dispatchers.Default) {
    println("1")
    launch {
      println("2")
      flow.collect { }
    }
    println("3")
  }
  println("4")
}
and got
Copy code
0
1
3
2
WTF?
Solution:
Copy code
CoroutineScope(coroutineContext).launch {
🚫 2
s
So the block that you pass to withContext is called on a CoroutineScope. And I think withContext waits for that CoroutineScope to complete. And since you call launch on that CoroutineScope, the CoroutineScope is not completed and withContext() will not return until the Scope (and your launch) completes. Not sure though.
👌 1
r
Nah, it does complete, see the
3
in the output
s
@reactormonk the 3 is still within the "withContext"
j
The scope does not complete until the flow completes (at which time you'll see 4)
r
Aye, so it does link them together
j
This is the general principle of structured concurrency
u
The
withContext
docs say nothing about waiting for a scope to complete:
Copy code
Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result
I thought for such a behaviour you’d need a
coroutineScope { }
block
s
@uli you are correct, I couldn't find it in the documentation either. I looked into the implementation.
u
This sounds like a bug to me. Andn ot a documentation bug … It’s rather unexpected
s
@uli that depends what you expect when you launch on the scope of withContext. When launching nested, generally I expect the structured concurrency to wait for it. At the same time, withContext doesn't specify it. So 🤷. Just a reminder to not launch on some random scope.
Sorry, that was not entirely correct. Let me clarify. I expect the scope to wait for any launches. Not the parent coroutine
u
But you don’t start a new scope. Why would you wait for it to finish then? What if you are already on
Dispatchers.Default
? Would it also wait for the launched coroutine? I.e. is the result of this the same? (Note the with context on top of the method)
Copy code
suspend fun someFun() = withContext(Dispatchers.Default) {
  println("0")
  withContext(Dispatchers.Default) {
    println("1")
    launch {
      println("2")
      delay(10000)
    }
    println("3")
  }
  println("4")
}
s
I would expect the result to be the same. The withContext method starts some new scope that it provides to the block.
I guess one could argue withContext should not be providing that scope
j
You can only launch into a scope, so how else would you be able to call launch if there was no scope created?
s
You should launch into a scope that you know. Preferable not GlobalScope but your own scope
u
as you are a supend fun, you are already running in a scope
j
You are running in a scope but you don't have access to it
s
yes but you can't launch just in any suspend fun. Launch is a function on the Receiver CoroutineScope
u
You are right. The surprise is the fact that your lambda (
block
) is an extension on CoroutineScope
Copy code
suspend fun <T> withContext(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T
s
And the documentation doesn't mention anything about this scope. Indeed strange. I wonder what the intended usecase for that scope is
👍 1