5 ms·
It's not a misunderstanding on my part, as far as I can tell... if you have any "await" statements in your asynchronous callback, you automatically lose any gua
by coder543 5y ago
It's not a misunderstanding on my part, as far as I can tell... if you have any "await" statements in your asynchronous callback, you automatically lose any guarantee of ordering as the runtime yields to other tasks. You do not know whether Task A or Task B will complete first. All you have control over is "critical sections", which will not be interrupted, but also can't contain any I/O (which would yield the executor) or long-running computations (which would lock up the UI) whatsoever. Even the order in which these critical sections are executed across multiple tasks is not guaranteed, so you can't rely on Task A to complete critical section 3 before Task B reaches critical section 2. It isn't ordered!
The only apparent benefit of needing to invoke "await" to break up your implicit critical sections is if you have a crap ton of global state (beyond the UI presentation layer) that you're manipulating without any kind of explicit synchronization at all. That's hardly an inspiring design pattern. You want to talk about messy UIs... that's the kind of mess JavaScript is classically known for, since people could always rely on the single-threaded nature to store everything as a global and manipulate it without a care in the world. Global state should be used sparingly. It's not just an antipattern for maintainability reasons, global state is also generally bad for performance since compiler optimization passes can't be as aggressive around it, and if you ever are running in a multithreaded context, manipulating global state significantly hurts CPU performance as the various cores have to keep synchronizing that global state back and forth.
Perhaps you would like to explain what "control of ordering" means in your context, because not being able to control the order that concurrent tasks complete is a very clear consequence of yielding control to the executor with an "await" statement. If you never yield, then sure, but... that's not very useful in an asynchronous context. You might as well just go back to writing blocking C event loops where every task always runs to completion before any other task can start.
- jayd16 5y agoThe difference between async/await and pre-emption is that execution will only yield at an await instead of possibly any time. You are guaranteed that two tasks without awaits will not be run concurrently. The magic of async/await is that you can opt out of the yields by avoiding awaits...or yield when you want. You do not need lock style critical sections because every command between yields are essentially a critical section. You can't avoid pre-emption. And yes, you _can_ guarantee task order A then B by having Task B await A. However, you're focusing on task order, I'm mostly talking about the order of instructs of a tasks acting like critical sections. Said another way, async/await lets you queue several critical sections in a convenient way. It also gives you the tools to do this on specific scheduling contexts and without thread marshaling or synchronization. You can bemoan the fact that UI systems are designed around single thread access, that that's messy or whatever but its just the reality. You mention thread synchronization. Exactly so. One of the reasons these frameworks opt for a single UI thread! Every popular UI framework works this way. Dealing with UI threads is inescapable. Running code on a UI thread to access UI state in a serialized way is extremely common and imo async/await handles it well and implicit threading doesn't.
- coder543 5y ago> The difference between async/await and pre-emption is that execution will only yield at an await instead of possibly any time. Yes, that’s a critical section. Every thread on your computer is constantly being preempted by the OS thread scheduler. It doesn’t matter to your code, just like it doesn’t matter in a preemptive task system, unless you are manipulating global state without a lock and someone else is trying to manipulate it behind your back. Preemption is a feature, not a bug. > And yes, you _can_ guarantee task order A then B by having Task B await A. What…? The whole point is that these are separate tasks started by the GUI framework as asynchronous callbacks. They can’t wait on each other because they don’t know about each other. If you think Go code can’t run its own instructions in a specific order… what?! So of course Go could do that too. But that’s not at all what I’m discussing! > However, you're focusing on task order, I'm talking about the order of instructs of a tasks acting like critical sections. I just don’t feel like you know how Go works. The order of instructions is the order you write them, barring any funny compiler optimizations which also apply to C#. It’s not “magically” running all instructions of your function in parallel or something. Whether the task gets interrupted or not is irrelevant — it will resume where it left off. As long as you synchronize access to global state, no one will be observe the interruptions to the task, exactly like how your operating system is interrupting your program constantly and you can’t even tell. “Implicit threading” isn’t even the right term here, since that implies the compiler or runtime is automatically forking your code into parallel sections for efficiency. Go does not do this. If you write a for loop, it executes every loop iteration sequentially. It doesn’t do wacky things like you seem to believe. > You can bemoan the fact that UI systems are designed around single thread access, that that's messy or whatever but its just the reality. That’s not at all what I’m “bemoaning”. If that’s what you’ve gotten from this conversation, then I’m utterly bewildered. I’m done trying to get my points across if communication has broken down to this degree.
- jayd16 5y agoParallel and concurrent are not the same. I'm talking about preventing concurrency of UI tasks. You can guarantee A1-A2-B1-B2 instead of A1-AYield-B1-B2-A2 if you have control of what will yield. With pre-emption, A might unexpectedly yield mid-execution and B could run, even when scheduled to a single thread. >Whether the task gets interrupted or not is irrelevant — it will resume where it left off. As long as you synchronize access to global state [...] Cooperative multithreading like async/await is leveraging the fact that actually if you control the interruption, THAT can be your synchronization. You don't need locks. UI programming is usually lock free. A UI thread is more commonly used. Understanding that, you can see that pre-emption _is_ sometimes a bug.