https://kotlinlang.org logo
e

Exerosis

06/18/2022, 4:50 PM
What is the point of CoroutineStart? How does Android avoid deadlocks if Dispatchers.Main isn't the same as Dispatchers.Main.immediate? What is the deal with Atomic starts and unconfined executing if they are already canceled? What abstraction is afforded by CoroutineDispatcher, ContinuationInterceptor etc.? It seems like functions like yield, continuation.intercepted(), etc. invalidate all of that by only being compatible only with specific dispatcher implementations. Is it possible to safely make a job that cancels and then becomes no longer canceled later? Why does Dispatchers.Unconfined have an event loop and in the example:
Copy code
withContext(Dispatchers.Unconfined) {
   println(1)
   withContext(Dispatchers.Unconfined) { // Nested unconfined
       println(2)
   }
   println(3)
}
println("Done")
Not always print 1, 2, 3 shouldn't the existence of the unconfined dispatcher have 0 impact on a block of code that doesn't make suspending calls? Is it possible for withContext to modify the way something executes if you aren't inserting a different dispatcher? What is the meaning of:
The invocation of cancel with exception (other than CancellationException) on this supervisor job also cancels parent.
Isn't is the case that every "well formed" call to cancel has either a CancellationException or null? Does that mean cancel() doesn't cancel parent but cancel(CancellationException()) does? Is that a standard understanding for jobs? Very much struggling to understand how to adapt all of this to my system in an "idiomatic" way.
n

Nick Allen

06/18/2022, 7:30 PM
Coroutine start let’s you define lazy async coroutine that doesn't cost you anything unless and until you need it (maybe a web request) or an atomic coroutine that is taking over responsibility of a resource (file or socket?) you need to close so it at least needs to actually start in order to close the resource if nothing else.
Why would android deadlock from the main dispatchers?
The continuation interceptor is built into the stdlib. Dispatchers are in the coroutines library and provide extra functionality that the library relies on. A completely different coroutine library (anyone could write one) would still presumably use the interceptor.
The point of yield is to let other coroutines run, if there's no other coroutine…then there’s nothing for it to do. That's not “invalid”. It’s kind of a waste that some dispatchers suspend in this scenario, just to resume the same coroutine, but it's really an implementation detail on how easy or difficult it is to detect that scenario.
The example code is just wrong for what’s trying to be explained. That code will always print the same thing no matter what implementation is used. Replace the second withContext with launch, then it'll actually make some sense. Does the dispatcher run launched code first or queue it up for later? That’s an implementation detail.
The doc linking to cancel is out of date. Any throwable used to work but that overloads was deprecated. The point that comment is trying to make is that it ignores child failures, but if it fails itself, then that failure is not ignored and propagates up.
e

Exerosis

06/22/2022, 12:59 AM
Sorry for the delay @Nick Allen thanks for clearing so many things up! I'll look into lazy starts more, I'm not sure I understand when the lazily started coroutines actually run at all. What is the point of undispatched if atomic exists then? Android could deadlock in this situation (presumably):
Copy code
specialCancellableButtonOrSomething.onClick { event ->
  if (!runBlocking(Dispatchers.Main) { someCallbackOrSomething() })
    event.cancel()
}
Because the button onClick is on the main thread and so if we dispatch is going to schedule a task rather than running the task right away on the current thread (immediate) then until the onClick listener returns the someCallbackOrSomething() cannot be dispatched... However the onClick listener cannot return until after it has been (deadlock). Couldn't you just give dispatchers canYield and yield() or just yield and they suspendUninterceptedOrReturn where they internally return if they cannot yield and suspend if they can? IG I still don't understand properly or something. Ah yes launch would make more sense. If I understand correctly the thread responsible for running launched tasks is either what thread resumes the withContext, or the thread that called launch. Thanks again!
n

Nick Allen

06/22/2022, 2:09 AM
For lazy:
Copy code
val result = myScope.async(start = CoroutineState.Lazy) { //doesn't run
    println("running")
    delay(100)
    42
}
delay(1000) //still doesn't run
println("result=${result.await()}") //now it'll start, wait 100, and then we'll print "result=42" 1100 millis after the coroutine was created
println("again, result=${result.await()}") //Will print immediately, it only runs once
Undispatched is more of an optimization, to avoid unnecessary thread switches. It takes extra time to schedule and wait for the scheduled task to actually run. It's extra convenient when you know you are already on the dispatcher you want. You should not be calling
runBlocking
from a click listener. It blocks which you should never do on main thread. Even if passing in other dispatchers. You can launch a coroutine using Dispatchers.Main and then you get the benefits of coroutines and can modify UI elements. runBlocking is more for APIs like OkHttp that have a private thread pool and then invoke callbacks on that thread pool expecting you to block so runBlocking is ok. Of course they could have designed yield differently to always suspend ... but why? It does what it's supposed to do which is pass control back to the dispatcher. If you describe your usecase, then I can probably redirect you to the right API to use.
e

Exerosis

06/23/2022, 1:11 AM
But how can you avoid using runBlocking when you are implementing systems? Obv the ideal situation is one where the onClick listener itself is a suspend fun and then there is no issue, but when you interface with java applications and frameworks you often don't have a choice. For example, if you launched in the onClick listener then you could not modify the click event in any way. I don't have a usecase in mind for yield right now, I was just curious as to why there was so much casting going on behind the scenes.
n

Nick Allen

06/23/2022, 3:51 AM
If you need to do something in the click event, you do it immediately before starting your async/background work. If your design requires you to wait for async/background processing and then do something in the click event, your design is inherently broken and needs to be revised. This as true for
runBlocking
as it is for
Future.get
.
59 Views