Like, why can't my sync function await something asynchronous? If it has to lock up the whole thread while that function executes, that's fine because that's how it was going to work anyway 99% of the time
Like, why can't my sync function await something asynchronous? If it has to lock up the whole thread while that function executes, that's fine because that's how it was going to work anyway 99% of the time
The answer, at least for Python, is that it is an intentional limitation because the alternatives introduce some quite bad trade-offs.
Option 1: your awaited promise goes into the main async event loop. This is bad because it means that your single-threaded sync function now needs to be thread-safe, and so does any sync code that calls your sync function despite it not even knowing that you're doing anything async. This is essentially unworkable without throwing away the option of writing non-thread-safe code.
Option 2: Your awaited promise goes into its own new event loop that only contains sibling and child promises. There's nothing technically stopping someone from doing this[1], but now you've lost a ton of the value of async because you will inevitably end up with a ton of siloed event loops that leave the process idle despite other async tasks existing that could run. Effective async code needs to share an event loop at as high of a level as possible, which means tainting as many methods with async as possible. At that point, you might as well enforce it at the language level and avoid the inevitable pain and fragmentation that comes from other devs across the ecosystem mixing sync and async code.
[1] https://pypi.org/project/nest-asyncio/
As explained by Guido: https://github.com/python/cpython/issues/66435#issuecomment-...
Implicit thread safety across async blocks of course break if you introduce actual shared multithreading in the language, while if you have atomic blocks at least you can build transactional memory on top.
I agree we shouldn't need to `await` everything though. Effects with inference and implicit perform/await is possible
Plus, you probably don't want to lock up the whole thread if you're writing anything more than a quick script, like a web server or a GUI.
This is why asynchronous I/O APIs became prevalent in JavaScript (initially with callbacks, with promises and then async/await syntax added later to make things nicer). Ryan Dahl then realized that this could also be used in a server-side context and would thereby solve the C10K problem, and so Node.js was initially designed with that same discipline (which was later relaxed a little with APIs like fs.readFileSync).
If Brendan Eich had realized in 1995 that this was how things were going to work, perhaps he'd have added green threads and browser events would spawn new ones (and so could block without locking up the page), but that's not the order things happened in.
I like this general approach a lot, it's overall quite nice for Julia's core use case of number crunching, it means you typically make decisions around concurrency at the call sites. Though it does rely heavily on Julia's runtime, and it can be a bit difficult to figure out what's going on under the hood.
It can in C# (just call .Result). I'm not sure why other async/await languages, like JS, don't just add that too.