[HN Gopher] Breaking Up with Long Tasks or: how I learned to gro...
___________________________________________________________________
Breaking Up with Long Tasks or: how I learned to group loops and
wield the yield
Author : jcbhmr
Score : 179 points
Date : 2025-01-04 03:51 UTC (19 hours ago)
(HTM) web link (calendar.perfplanet.com)
(TXT) w3m dump (calendar.perfplanet.com)
| leeoniya wrote:
| the problem is that async-ification infects everything up the
| call chain. converting a synchronous loop to async may now
| require that you change a lot of your sync code above that loop
| into async code as well, which can complicate all sorts of stuff
| like debugging, profiling, error handling.
|
| for this reason i always optimize the shit out of all synchronous
| code before resorting to async. and if you do need to go async
| after that, you might be better off just offloading that
| optimized synchronous op to a worker thread (or to wasm) to not
| block the UI.
|
| modern JS vms are insanely fast when you pay attention to GC
| pressure, mem allocation, monomorphism, and dont use accidentally
| quadratic algos. it's rare that i encounter a codebase that can't
| be sped up by some large multiple without ever resorting to an
| async/defer crutch.
| ordu wrote:
| _> you might be better off just offloading a synchronous op to
| a worker thread to not block the UI._
|
| I believe it should be the answer. If your computations are
| tolerably fast, then you could do it without async, but if they
| are not, then it is better to use preemptive multitasking for
| them. The overhead on the kernel scheduler will be small,
| because you don't start 10k of threads concurrently eating CPU
| time. Probably the overhead of starting a thread doesn't matter
| either with long tasks. As a bonus you could also do blocking
| i/o operations without thinking about blocking.
| mikepurvis wrote:
| Is this a JS specific issue? I find python is decently friendly
| to having little pockets of async where it makes sense in what
| is otherwise a regular synchronous program.
| Too wrote:
| I'd say it's the other way around. In JS, async is just
| syntax sugar on Promises, they still execute within the same
| event loop. So regardless of if you are in async or not, you
| always have to think about not blocking. This becomes a lot
| easier to reason about, because all code is from the
| beginning made to be non blocking. Whereas in python if you
| call a blocking sync function from async world you are up for
| trouble. The problem solved in the OP is the unusual case
| where you need to do some big sync computation.
| Yoric wrote:
| My experience is that Python is worse at async than
| JavaScript. At least, the debugging experience quickly scales
| up to nightmarish.
| MzHN wrote:
| If this was any other language than JS I would agree but my
| personal experience with JS is the opposite.
|
| In my experience almost everything in the JS world is already
| async. User interactions are async, requests are async, almost
| all NodeJS APIs are async. To me having to add more async in JS
| is a tiny barrier compared to what I'm facing in other
| languages that feel more synchronous to me.
|
| Since there is already so much async I also feel like
| debugging, profiling and error handling are all pleasantly
| solved problems in JS.
|
| Offloading to workers is also async so while there are many
| valid benefits to be gained, avoiding async does not seem like
| one of them to me.
| fenomas wrote:
| > you might be better off just offloading that optimized
| synchronous op to a worker thread (or to wasm) to not block the
| UI.
|
| It works in principle, but note that this really complicates
| your build process. In particular, if you're writing a library
| that other people will use as a dependency, there's really no
| good way to use workers at all without affecting how people
| bundle their code using your library.
| adregan wrote:
| The library use case is trickier, but bundlers do a pretty
| good job of handling workers (albeit with funky magic
| comments in webpack's case).
|
| What I find a pain is the uneven support for shared workers.
| bboygravity wrote:
| Does anyone know when we went from calling things serial and
| parallel execution to sync and async?
|
| Was it before or after we started calling "man-hours" "story
| points"?
| rplnt wrote:
| Async and parallel are not the same thing. You can run code
| async and yet not parallel (one core, no i/o).
| abtinf wrote:
| JS, as typically run in a browser, is both async and single-
| threaded.
| com2kid wrote:
| Async in JS is not in parallel, which is a very -very-
| important distinction for program correctness.
| Yoric wrote:
| Async and parallel are a bit different.
|
| Parallel means that things execute at the same time (or at
| least appear to do so). Async means that you yield control to
| some kind of scheduler and get it back at a later point.
|
| Barring workers, JavaScript actually guarantees that two
| pieces of code never execute at the same time. That's the
| run-to-completion semantics.
|
| When async was introduced to JavaScript from two different
| angles (callbacks in Node, Promise then await in browsers),
| there was limited parallelism involved (typically running I/O
| in the background), but the async was meant to ensure that:
|
| 1. while a task was (almost explicitly) waiting, another one
| could run;
|
| 2. in the case of the browser, we could yield control from
| JavaScript often enough that the UI could run smoothly.
| pavlov wrote:
| Node didn't introduce callbacks to JavaScript, they were
| present in the earliest browser APIs (img.onload, etc.)
| Yoric wrote:
| Fair enough, I meant CPS-style programming.
| layer8 wrote:
| These were just regular events on the UI thread, not any
| different from _onclick_ etc., IINM.
| pavlov wrote:
| So, the same thing as callbacks in Node.
| fenomas wrote:
| When people talk about a callback in this kind of context
| they usually mean one function passed as an argument to
| another, in order to be invoked with the latter
| function's return value. Not event handlers like onclick,
| etc.
| layer8 wrote:
| I'm not familiar with Node, but static event handlers
| aren't usually referred to as callbacks in that context.
| "Callback" implies that something is being called, for it
| to call _back_ to the client code. That's not what's
| happening with the _onxxx_ event handlers. There is no
| "back" there. The event loop simply calls "forward" into
| the JavaScript code.
| pavlov wrote:
| I don't know what you mean by "forward" because there is
| no difference between these two:
| img.onload = function() {...} img.src = "some url"
| img.load("some url", function() {...})
|
| The early JavaScript APIs use the first style, but the
| result is the same.
| johannes1234321 wrote:
| Parallel code runs parallel at the same time (on different
| CPU cores etc.)
|
| Async code is scheduling different parts after each other.
| It's still running in a single thread (maybe on same core)
|
| aaync essentially is non-blocking IO in many cases. It can
| also ahieldnsome logic running in a diffefent thread, hidden
| from that segment of code.
| 333c wrote:
| I think your explanation of async, while true, doesn't get
| at what's special about async. The explanation seems true
| of (non-parallel) concurrency as well (for example, thread
| scheduling on a one-core CPU).
| bsimpson wrote:
| It's insane that "don't block the UI thread" is so complicated. I
| never would have expected half the article to be about an edge
| case if the user is in a background tab.
|
| What's worse is I don't even know how you're supposed to know
| what the edge cases are without stumbling into them. You
| shouldn't have to know about APIs like document.hidden unless you
| specifically want to handle background tabs differently. They
| shouldn't leak into the regular event loop.
| PittleyDunkin wrote:
| I just wanna note it's crazy that things confusing people a
| decade ago are still confusing people now. How fucking hard can
| the concept of a ui thread be?
| hahn-kev wrote:
| I think part of the problem is that there is no other thread
| (outside of service workers), so that's why it's hard. In C#
| I would just fire up another thread to do the work and I
| don't have to worry about blocking the UI until I want to
| notify the UI from that thread. But I can't fire up a
| separate thread in JS so everything is done on the UI thread
| PittleyDunkin wrote:
| > But I can't fire up a separate thread in JS so everything
| is done on the UI thread
|
| Yea targeting the web is nuts if you have other options!
| chii wrote:
| The browser as a runtime is the epitome of worse-is-better.
| rafram wrote:
| Outside of _workers_ , of which service workers are one
| type.
| Yoric wrote:
| I don't think it's the problem.
|
| I think that it's a combination of two things:
|
| 1. many webdevs just have no clue what a thread is, because
| generally, they don't need it, so it isn't taught;
|
| 2. most of the documentation you can find online was
| written by people (and now ChatGPT) who don't understand
| async, sprinkle the word randomly and tweak things until
| they seem to work.
|
| As a consequence, webdevs learn that async is _magic_.
| Which is a shame, because the underlying model is almost
| simple (the fact that we have both micro-tasks and tasks
| complicates things a bit).
| bsuvc wrote:
| > many webdevs just have no clue what a thread is,
| because generally, they don't need it, so it isn't taught
|
| JavaScript doesn't have threads.
| Retr0id wrote:
| This is not meaningfully true in 2024
| Retr0id wrote:
| *2025 (although still true of 2024 and several preceding
| years, heh)
| soulofmischief wrote:
| Parent commenter helped implement async in JS, they know
| what they are talking about. JS has threads locked behind
| semantics. Web workers run on separate threads. I do a
| lot of heavy parallel processing that never blocks the UI
| with them all the time.
| bsuvc wrote:
| I stand corrected.
|
| It looks like web workers is the way for JavaScript to do
| multi-threading.
|
| Async has always been enough for what I need to do in the
| front end, as most of my long running processes are just
| calling a back end.
|
| Edit to add: for context, I am a full stack developer and
| know what threads are... I just never have needed them in
| the browser.
| soulofmischief wrote:
| Web workers are great for local compute and isolation.
| Unfortunately it's a hassle managing sane pooling because
| different platforms have different worker limits.
|
| On the other hand, the isolation guarantees are strong.
| There aren't really any footguns. Messaging is
| straightforward, works with a lot of data types and
| supports channels for inter-worker communication.
| WJW wrote:
| I don't think that is weird at all. Nobody is born knowing
| about UI threads and junior devs today are not significantly
| different in knowledge from junior devs a decade ago. It's
| not surprising that things which were confusing in 2015 are
| still confusing now.
| santoshalper wrote:
| That's what I love about these junior devs, man. I get more
| experienced, but they stay noobs. All right, all right, all
| right.
| Ma8ee wrote:
| I read somewhere that about 50% of all developers have less
| than 5 years of experience. That fact explains quite a lot.
| greggyb wrote:
| I've come across that as well, but in presentations. I
| think it is either Alan Kay or Bob Martin who likes to use
| the statistic. I recall it being framed in terms of the
| growth of tech/programming as a profession: it's doubled
| every 5 years; the fact that 50% of developers have less
| than 5 years of experience. This is simply exponential
| growth stated in two different ways.
|
| I do a lot of work on a platform that has doubled in user
| base yearly since its inception, for about a decade (though
| I think we are at an inflection point now or soon), and it
| is wild to have "experts" be those with 2 or 3 years of
| experience. Having used the platform for 11 years, now it
| is crazy to believe I have more experience than 99.9% of
| the field.
| normie3000 wrote:
| > things confusing people a decade ago
|
| I was being confused by blocked UI threads TWO decades ago
| (AWT or Swing or something), so I'm confused that it was
| confusing you one decade ago.
| moregrist wrote:
| To be fair, blocking the UI thread is still a common issue in
| desktop app programming, where it's been a well-known thing
| for at least 1-2 decades longer.
|
| You either have to split linear execution (hard although
| async can make this easier in languages like Python) or go
| multithreaded with its own difficulties.
|
| But IME what typically happens, at least in desktop apps, is
| that doing a little work in the UI thread is fine until it
| isn't, and the "not fine" moment can be months or years away
| from when the code is written. The bug reports are often
| vague and rare enough that it takes a while to see the
| pattern (eg: "it froze and then I killed the program"). And
| after enough of these problems, someone with experience puts
| a framework and some rules in place that move tasks off the
| UI thread. Which works until they creep back in.
| Yoric wrote:
| It used to be even worse, with various tabs' event loops
| accidentally interacting with each other whenever a `alert()`
| was called.
|
| When we were (slowly, painstakingly) making the Firefox UI
| compatible with multi-thread and multi-process, we hit this
| kind of issues all the time. One of the reasons we introduced
| Promise and async/await (nee task/yield) in JavaScript was to
| give us a fighting chance to at least look at the code and
| understand where it could all break. And then we had to come up
| with the tools to actually debug these...
| severine wrote:
| But you did, and Firefox flies these days, thanks!
| ramon156 wrote:
| Thanks for having made the internet at least a little bit
| more bearable!
| gttalbot wrote:
| What's old is new again. Some of the same techniques used to keep
| pre-MacOS-X applications responsive, back when MacOS was
| cooperatively scheduled, show up here.
|
| This begs the question of what is a reasonable programming model?
| In the MacOS case, the forcing function was buying NeXT, using
| their Unix kernel for MacOS, and literally firing the OS
| engineers who disagreed with preemptive multitasking.
|
| For these browsers, is there a programming model that could be
| instituted where processing in these handlers didn't hold up the
| main UI thread?
| wongarsu wrote:
| The previous decade certainly feels like a big resurgence of
| cooperative multitasking, in the rise of JavaScript and the
| rise of async in all languages.
|
| Making JavaScript (conceptually) runs in the UI thread was imho
| one of the mistakes owed to the extremely simple early versions
| of JavaScript. We would be better off if JavaScript was
| preemptively scheduled (whether by the browser or by the OS)
| with language primitives to lock the UI in specific execution
| sections.
| ciconia wrote:
| Locking the UI while doing a lengthy operation is hardly an
| acceptable solution. A better solution would be to indicate
| to the user an async operation is in progress, and optionally
| provide a button for canceling the operation.
| wongarsu wrote:
| What I was thinking about is that you do need a way to lock
| the UI when running multiple statements that update the UI.
| Something like a lockUI{} block (conceptually a critical
| section holding a lock on UI updates). This would for
| example allow you to prevent a situation where you have to
| change two data attributes on a button and the user clicks
| the bottom between those two updates. It would be on the
| programmer to keep those lockUI{} sections as short as
| possible.
|
| If JavaScript 1.0 had included such a primitive you could
| run all other JavaScript in the background. Alas, the
| JavaScript we have is essentially putting all code into
| such an lockUI block, and this assumption is baked in to a
| lot of code
| pavlov wrote:
| If that language feature had been included, the early web
| would have been filled with tutorials that say: "Don't
| forget to wrap all your code in lockUI{} because that
| guarantees it runs correctly and things don't suddenly
| change behind your back!"
|
| And then we'd have the popular web frameworks just taking
| the lock and running all user code inside it, and
| everything would be the same as today.
| greggyb wrote:
| But we'd have _options_. ~There aren't any today.~
|
| Ninja edit: there haven't been any until workers.
| jebarker wrote:
| As others have commented, this seems way too hard to achieve. But
| it warms my heart to see web developers caring about performance.
| dleeftink wrote:
| For really heavy work I think hamsters.js is pretty cool[0].
| GPU.js can also make a huge difference, but might be overkill for
| some applications.
|
| [0]: https://github.com/austinksmith/Hamsters.js
| bennythomsson wrote:
| Isn't the whole approach wrong? All that stuff should be in
| another thread. That's it. Re-inventing a bad version of
| cooperative multitasking, in 2025, really?
|
| It's surely a nice exercise, but I really hope this is not
| production code anywhere. If it is then I'm not surprised about
| the current state of web software (myself working in a very
| different area, so I don't really have a clue).
| thdhhghgbhy wrote:
| I'm surprised the solution ended up having a magic number in it.
| I realise the initial guess is refined, but in a commercial
| scenario I see this code aging poorly and misbehaving at some
| point, after the whizkid who wrote it has left.
| geon wrote:
| Is anyone using generators and the yield keyword to do
| concurrency in js?
___________________________________________________________________
(page generated 2025-01-04 23:00 UTC)