Eg. Don't try to sleep accurately between the simulated cycles. Let things happen "too fast" for longer and do the delays in batches.
Not sure if this would wind up perceivably too bursty. Just an idea based on things I have seen in other domains.
For the record I don't think spinning is too bad of it's a very short duration, shorter than the scheduler will give you when you take your thread off the CPU.
https://docs.microsoft.com/en-us/windows/desktop/api/synchap...
I could mess around with the Win32 API or the NT API or something to make the heartbeat rate more granular, but according to the docs that kills battery, so I'm guessing it's going to be doing pretty much the same thing as what I'm doing manually.
1) post tasks to process some data
2) while(tasksNotFinished) Sleep(1);
Well, very quickly found out that the Sleep(1) was always a minimum of 16ms(or a multiple of 16ms), which made the whole thing stupidly slow. We fixed it by running timeBeginPeriod(1) which overrides the system timer resolution to 1ms. Yes it would kill the battery on laptops, but on a server this is not an issue.
Back when everyone was targeting 60fps, >~2.25ms of 'leftover time' was needed during the individual 16.6667ms run of that loop to actually trigger sleep instead of spinning.
Note that's specific to the graphical rendering thread, and things like the mouse/kb input have their own sleep durations, with or without thresholds.
Once your emulation time period between two screen frames is completed, you can pause emulation until it catches up to next frame in real time, making process sleeping and not consuming CPU in between frames. Input events and sound add additional complexity, but it's doable.