Any plans for making `startCoroutineUndispatched` ...
# coroutines
y
Any plans for making
startCoroutineUndispatched
public or accessible in some way? I can use
CoroutineStart.invoke
with an opt-in, but of course that opt-in scares me! I want something slightly more "elegant" for lack of a better word than:
Copy code
launch(start = CoroutineStart.UNDISPATCHED) { continuation.resumeWith(runCatching { f(receiver) })
d
No plans that I know of. I don't think I've seen
startCoroutineUndispatched
being useful in some typical use cases. What is the end goal of the code you provided?
y
I want to run code synchronously until the first suspension for an optimization I'm implementing. The lambda I'm running is user code, but if it happens to not suspend until calling a special method from my library (which always calls
suspendCoroutine
), I'd like to optimize that first call. I can do that in a different way of course like e.g. taking the arguments of that first call as a parameter, but that's inelegant. I can also do the launch as above, but I'm concerned that that might not have the right semantics
d
I'm not sure I got it. So,
Copy code
yourFunction {
  delay(1.seconds)
  anotherFunctionOfYours()
}
should suspend in
anotherFunctionOfYours
, but
Copy code
yourFunction {
  println("not going to suspend")
  anotherFunctionOfYours()
  println("told you!")
}
shouldn't?
y
No, no. It should run syncly until first suspension, and if that suspension happens to be
Copy code
suspend fun anotherFunctionOfYours(param: Blah) = suspendCoroutine { //store cont and param }
then
yourFunction
should do something special. `yourFunction`s to a previous isn't suspend, and in fact it connects the block it got passed to a previous continuation. Basically, if it doesn't get that first call, it has to terminate execution and wait until it gets called (because it needs the
param
value), but if it does get that first call, it can go on a faster path where it uses the
param
value immediately.
So this one goes on the "slow" path:
Copy code
yourFunction {
  delay(1.seconds)
  anotherFunctionOfYours(blahCalculationThatMightBeSuspend())
}
and this one on the fast path:
Copy code
yourFunction {
  println("not going to suspend")
  anotherFunctionOfYours(42)
  println("continuation got resumed")
}
and the point is to capture that
blah
parameter if it's sync-ly available, but if not we can wait for it (this sounds so suspiciously similar to how suspension works, I know. I'm experimenting with making delimited continuations using Compose, hence why I'm going deep into coroutine machinery)
d
So, to this end, you do something like this (pseudocode)?
Copy code
suspend fun yourFunction(block: suspend Registrator.() -> Unit) {
  val registrator = Registrator()
  coroutineScope {
    launch(start = CoroutineStart.UNDISPATCHED) {
      registrator.block()
    }
    if (registrator.hasRegistered) {
      // we entered the fast path
    } else {
      // we suspended before registering
    }
  }
}

fun Registrator.anotherFunctionOfYours(value: Int) {
  register(value)
}
y
Yes, very similar. Only difference is that
yourFunction
is not suspend, and
block
is used to resume another Continuation (and hence
block.startCoroutine(registrator, continuation)
is the perfect call here). Also,
anotherFunctuonOfYours
looks more like this:
Copy code
suspend fun Registrator.anotherFunctionOfYours(value: Int) {
  register(value)
  suspendCoroutine {
    this.continuation = it
  }
}
z
Couldn’t you do something like this? https://pl.kotl.in/BIyYvgKfX
đź‘€ 1