4 ms·
I claimed that thread identity was an unnecessarily low level concern for most developers to need to worry about, not threading itself. Especially if "most deve
by coder543 5y ago
I claimed that thread identity was an unnecessarily low level concern for most developers to need to worry about, not threading itself. Especially if "most developers" are just running async callbacks in a single threaded event loop, then who cares about thread identity? Not most people, that's for sure. JavaScript certainly never makes you think about thread identity, and it is one of the most popular languages for UI development. You could run a Goroutine-style lightweight threading system inside a single thread too... it's not strictly necessary for it to be as advanced and multi-threaded as Go's default implementation.
> You keep claiming implicit threading is better for UI but you have no examples.
"Implicit threading" isn't causing problems here. When you are running multiple async/await tasks at the same time, you have no guarantee of ordering there either, you just get all the downsides of additional syntactic bloat and the possibility of accidentally blocking the executor because the executor isn't preemptive -- it's cooperative. If someone clicks a button and that spawns an async callback task, and then they click a different button that spawns another async callback task, as long as you don't block the executor with bad code, those tasks will run in any order that they please as time is available on the executor and as "await" calls unblock. If you're somehow only allowing (or only considering) the case where only one UI callback task is allowed to run at a time without making the UI feel locked up, then you have exactly as much control from within a Goroutine over the ordering of what happens as you would in async/await... but since you're (presumably, since you should be) in a separate goroutine from the UI thread, you cannot block the UI thread no matter what you do, even though you can if you are running a single-threaded async/await event loop and do anything wrong. A classic C# approach would be to launch a full OS thread for each callback handler, and then only synchronize with the UI thread to update UI state, and for many use cases that is arguably better than async/await. Full OS threads are typically considered too expensive to launch many of them, which is why async/await came about, but for small numbers of concurrent tasks... async/await doesn't offer much advantage over full threads.
To make this even more explicit, I would argue that a very large percentage of UI developers these days are developing SPAs, and most actual work that a SPA does is performed on the backend, not within the frontend javascript. As UI elements are interacted with, the SPA is firing off asynchronous requests through a load balancer, which does not even guarantee that requests will remotely go to the same backing server. Each server is acting as the callback handler from completely different machines, yet those developers do just fine without thinking about the thread identity of random UI handlers. This is extremely multithreaded and unordered.
The differences between classic async/await and goroutines/lightweight tasks are also a lot fewer than you seem to realize, they aren't radically different, but those differences represent significant improvements. You can emulate classic, cooperative async/await on top of preemptive lightweight threads, but you can't go the other way.
- jayd16 5y ago>you have no guarantee of ordering there either, Here's your fundamental misunderstanding. You absolutely do have control of ordering. Unless you explicitly yield execution, you know you have full control of that thread and will never be pre-empted. That is a useful tool used all over UI dev and game dev.
- coder543 5y agoIt'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.