Hacker News new | ask | show | jobs
by charcircuit 1010 days ago
async is not free. It will turn your code into a big state machine and each thing you await will likely create its own thread.

There is simplicity in a avoiding that and having code that gets compiled to something that is straightforward and single threaded.

3 comments

This is true of all abstractions; if you don't need them, then they'll make your program more complex and more painful to write and maintain.

Exercising judgement about when to use or shirk an abstraction is a lot of what being a software engineer is about.

When does async/await ever make your program harder to maintain? Maybe to people who don't already know it, but almost all the big languages have async, it would be hard for a programmer to get away with not learning it, at least if they're a python of JS programmer.

It adds complexity, but it's at the level where you don't have to think about it. If you're doing something advanced enough to where async is a leaky abstraction, you're probably doing something big enough to where you would want the advantages it offers.

If you're doing something simple, async is just a black box primitive that is pretty easy to use.

This is not true for Rust. Await in rust builds a larger state machine from the former. It does no implicit thread or task spawns (unless the future you're awaiting does them explicitly).

Furthermore, async rust can be run single threaded

What happens if I block a future in a single threaded runtime?
Then you block the runtime? _aha you got me, threads and pre-emptive concurrency is better_.

This is where you have a reasonable trade off. I have accepted that async gives me more control over my code. For that I have to accept that blocking can slow down the app. After running async rust in production for over 2 years now I've not seen any blocking tasks block the executor. Maybe I'm just good but my experience is that my colleagues who come from C# generally don't make these mistakes either

Awaits don’t create threads, at least not in any runtime I know of. There is usually a fixed number of threads at launch.
FastAPI docs, case when you don't create an async route

> When you declare a path operation function with normal def instead of async def, it is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server).

https://fastapi.tiangolo.com/async/#path-operation-functions

OP either meant this, or its variation, such as async_to_sync and sync_to_async. https://github.com/django/asgiref/blob/main/asgiref/sync.py

Ofc this is a python example. I have no idea how it works in different languages.

“run in a threadpool” isn’t the same as creating a thread though
NB: In Python >= 3.9 the idiomatic way to do this is to_thread(), not familiar with these ASGI functions but I would guess they're a polyfill and/or predate 3.9.

https://docs.python.org/3/library/asyncio-task.html#asyncio....

They are not polyfills. Multiple scheduling modes are provided for libraries that are not thread safe (it's a total mess and I avoid these wrappers like the plague)
I've had to weld some async and sync Python together with queues and callbacks, it's not pretty.
That is an implementation detail on where you put the code that is blocking or running concurrently from the main code. An executor could use a separate OS thread, or the application could itself schedule application levels threads onto a number of OS threads.

When writing a Future that will block for 5 seconds you will need to find somewhere to that you can put the code to block for 5 seconds. You don't technically need to even use an executor here.

I think they meant it was likely to spin off additional tasks/green threads.
If they meant that they are still wrong.
Tokio uses a pool of threads for disk I/O because it uses the synchronous calls of the operating system.