[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)