Hacker News new | ask | show | jobs
by didibus 1373 days ago
I've not normally seen people say async/await is a form of structured concurency.

As I understand, structured concurency mostly imply automatic cancellation and supervision that allows retry/restarts.

Does async/await gives you that?

2 comments

Automatic? I suppose not. But excuse me, I'm just comparing the APIs for a specific use case. I didn't mean to call them the same thing.
The additional features of structured concurency affect the API though, because you need to introduce scopes of supervision/cancellation/retries.

If any async operation fails, at what level up the waiting chain do you want to cancel all other concurent tasks under it and retry the entire async flow?

So you need the APIs/syntax to have ways to define these scope of cancellation/retries.

As I understand, this is why it is called "structured", it refers to similar "structured" programming that introduced lexically scope restricted procedures, conditionals and loops.

Edit: Also, just want to point out that you shouldn't get mixed up between async/await and future/promise. Async/await often uses future/promise, but don't necessarily have too. But future/promise is an async API that existed before async/await, and supports threaded concurency just fine. You can use future/promise in Java with both threads or virtual threads. Using it with virtual threads won't color functions because at any time you can block on a future/promise and extract the value, so it's possible to go from a future/promise returning function to one that doesn't return a future/promise.

As far as defining scope goes, C# gets away with unchecked exceptions. Exceptions just bubble up naturally. Task scope is more implicit in syntax but scheduling Context is used in a similar way to scopes. The difference is context is usually inferred instead of explicitly defined at every use.
It's not about the exception, it's about the exception handling.

If 4 tasks are started asynchronously and one of them throws? What do you want to happen to the other 3?

In structured concurency you could say, if any of them fails then cancel all others, and retry them all.

Or for example, if one task is waiting on another and that first task throws an exception, you might want to say, ok, retry the first and re-schedule the other one to wait on that retried task.

These are all things you can handle yourself as well, but structured concurency is just trying to make that less effort and automatic. So if you define a supervision scope, everything inside it no matter how complex the async graph is, it'll all cancel/retry appropriately at that scope.

The scope will also guarantee that when we leave the scope, all concurent tasks are done, nothing is left running, either because it's all cancelled or the scope waits for all tasks to complete.

Here's some good articles that explains the motivation for it:

https://elizarov.medium.com/structured-concurrency-722d765aa...

https://github.com/apple/swift-evolution/blob/main/proposals...

async/await is just syntactic sugar for futures/promises. The future API itself generally lends itself towards structured concurrency, but your particular implementation may not be.

For example, Rust's `Future` is structured (although executors may introduce escape hatches), while JavaScript's `Promise` is unstructured.

Can you explain a little how Rust adds cancellation and retry supervision using future? I'm not familiar with Rust.