>That may be when the "typically" happen, but you can still get race conditions where you have two tasks that can run next, and you accidentally write an assumption about which will happen into your code when there is no such assumption in the scheduler.
What are some async coding styles where this is prevented? I feel like making incorrect order-of-execution/arrival time assumptions could be an issue for any kind of async code.
I'm not sure 100% prevention is an option. Working in a language with very strong mutation control, either via immutability in Haskell or something very controlling like Rust, probably eliminates most of them, but I'm pretty sure in both cases it would still be possible to write a straight-up logic error where the programmer assumed a guarantee that did not exist.
Still, I recommend that approach personally. The best of all worlds is to write threaded code in languages that structurally eliminate the majority of issues threads have. The downside is that the "languages that structurally tame threads" is a pretty short list, even now; Rust, Haskell, Erlang/Elixir, see the probably-inevitable replies for a couple of others. You can widen the field substantially by working with libraries in languages that encourage that style of programming, but lacking the language support means you're back to exercising a lot of programmer discipline to ensure you stay in the acceptable subset of the language. This is a continuum based on the support available in the languages and the ability to get libraries, but this gets you to the Go language, Java/Scala's Akka, and some stuff like that. I'm still not really willing to put anything Python has into even the latter list, though; the dynamic languages are just so easy to blow your foot off.
(I still like "green threads" personally, because I write network servers where tens of thousands of simultaneous connections is a realistic concern and the price is a small one to pay. I tend to agree with the Rust community that people are generally grotesquely overestimating the run-time costs of threads nowadays. If you're in a language like Rust, and you're working in a program where you can be pretty confident you're never going to spawn 100,000 threads or face unbounded thread creation for some reason, go for it. Threads are not magically bad; they're bad for specific reasons, and if you address those specific reasons, they cease being ravening Elder Gods who eat your soul, and become just another professional-grade tool... yeah, it has some sharp points on it, but it does the job really well.)
What are some async coding styles where this is prevented? I feel like making incorrect order-of-execution/arrival time assumptions could be an issue for any kind of async code.