Live data from Hacker News

Async-std: an async port of the Rust standard library

async.rs

91–100 of 238 posts

Re: Async-std: an async port of the Rust standard library

#91

I must be dumb, because every time I dive into async/await, I feel like I reach an epiphany about how it works, and how to use it. Then a week later I read about it again and totally lost all understanding. What do I gain if I have code like this [0], which has a bunch of `.await?` in sequence? I know .await != join_thread(), but doesn't execution of the current scope of code halt while it waits for the future we are…

> I must be dumb

Nope, async really isn't trivial.

> I know .await != join_thread(), but doesn't execution of the current scope of code halt while it waits for the future we are `.await`-ing to complete?

It doesn't, that's the charm of it.

It's best to treat 'await' as syntactic sugar, and to dig in to the underlying concepts.

I realise we're not talking C#/.Net, but that's what I know: in .Net, your function might do slow IO (network activity, say) then process the result to produce an int. Your function will have a return-type of `Task`. Your function will quickly return a non-completed Task object, which will enter a completed state only once the network activity has concluded and processing has occurred to give the final `int` value.

The caller of your function can use the `Task#ContinueWith` method, which enqueues work to occur if/when the Task completes, using the result value from the Task. (We'll ignore exceptions here.)

Internal to your function, the network activity itself will also have taken the form of a standard-library Task, and our function will have made use of its `ContinueWith` method. Things can compose nicely in this way; `Task#ContinueWith` returns another Task.

(We needn't think about the particulars of threads too much here, but some thread clearly eventually marks that Task object as completed, so clearly some thread will be in a good position to 'notice' that it's time to act on that `ContinueWith` now. The continuation generally isn't guaranteed to run on the same thread as where we started. That's generally fine, with some notable exceptions.)

You might think that chain-invoking `ContinueWith` would get tedious, as you'd have to write a new function for each step of the way if we make use of several async operations - each continuation means writing another function to pass to `ContinueWith`, after all. Perhaps it would be more natural to just write one big function and have compiler handle the `ContinueWith` calls.

You'd be right. That's why they invented the `await` keyword, which is essentially just syntactic sugar around .Net's `ContinueWith` method. It also correctly handles exceptions, which would otherwise be error-prone, so it's generally best to avoid writing continuations manually.

There's more machinery at play here of course, but that seems like a good starting point.

Assorted related topics:

* If you use `ContinueWith` on a Task which is already completed, it can just stay on the same thread 'here and now' to run your code

* It's possible to produce already-completed Task objects. Rarely useful, but permitted.

* There's plenty going on with thread-pools and .Net 'contexts'

* The often-overlooked possibility of deadlocking if you aren't careful [0]

* None of this would make sense if we had to keep lots of background threads around to fire our continuations, but we don't [1]

* Going async is not the same thing as parallelising, but Tasks are great for managing parallelism too

* This stuff doesn't improve 'straight-line' performance, but it can greatly improve our scalability by avoiding blocking threads to wait on IO. (That is to say, we can better handle a high rate of requests, but our speed at handling a lone request on a quiet day, will be no better.)

I found this overview to be fairly digestible [2]

[0] https://blog.stephencleary.com/2012/07/dont-block-on-async-c...

[1] https://blog.stephencleary.com/2013/11/there-is-no-thread.ht...

[2] https://stackoverflow.com/a/39796872/

See also:

https://docs.microsoft.com/en-us/dotnet/standard/parallel-pr...

https://docs.microsoft.com/en-us/dotnet/api/system.threading...

Re: Async-std: an async port of the Rust standard library

#92

I must be dumb, because every time I dive into async/await, I feel like I reach an epiphany about how it works, and how to use it. Then a week later I read about it again and totally lost all understanding. What do I gain if I have code like this [0], which has a bunch of `.await?` in sequence? I know .await != join_thread(), but doesn't execution of the current scope of code halt while it waits for the future we are…

A good example is say you want to handle 100k TCP sessions concurrently. You probably don't want to launch 100k threads considering the overhead in doing so and constantly switching between them. You also don't want to do things synchronously as you'll constantly be waiting on pauses instead of doing work on the 100k sessions. So you launch 100k instances of it as an async function and they all stay in a single threa…

This is missing a crutial explanation that the underlying OS API are asynchronous.

Re: Async-std: an async port of the Rust standard library

#93
post #54

Earlier quoted context omitted.

Yeah if there are no other futures spawned, then the await is going to cause the app to just sit there until the future completes. It's got nothing better to do. If there was another future spawned then the await would cause the runtime to sit there until either of the futures completed. The code would attend to the first future that completes intil that hits an await.

And where it all comes together is when async lets us write concurrent functions that compose together well in a way that functions that can block on IO and lock acquision do not.

How so? The same API is trivial to implement using threads and futures. A future, after all, is just a one shot channel or rendezvous point.

Async is just a way to get cooperative threads compiled to a static state machine, trading lower concurrent utilization and throughput for less context switch overhead and lower latency.

Re: Async-std: an async port of the Rust standard library

#94
post #5

This remind me of the blog post "What Color is Your Function?"[0], they had to create a different library that is the same as the standard library but with async functions. I thought Rust had other, better ways to create non-blocking code so I don't understand why to use async instead. [0] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

> I thought Rust had other, better ways to create non-blocking code so I don't understand why to use async instead. In fact, Rust does have a great solution for nonblocking code: just use threads! Threads work great, they are very fast on Linux, and solutions such as goroutines are just implementations of threads in userland anyway. (The "what color is your function?" post fails to acknowledge that goroutines are jus…

> The "what color is your function?" post fails to acknowledge that goroutines are just threads, which is one of my major issues with it.

"""Three more languages that don’t have this problem: Go, Lua, and Ruby.

Any guess what they have in common?

Threads. Or, more precisely: multiple independent callstacks that can be switched between. It isn’t strictly necessary for them to be operating system threads. Goroutines in Go, coroutines in Lua, and fibers in Ruby are perfectly adequate."""

What more do you need?

Re: Async-std: an async port of the Rust standard library

#95
post #5

This remind me of the blog post "What Color is Your Function?"[0], they had to create a different library that is the same as the standard library but with async functions. I thought Rust had other, better ways to create non-blocking code so I don't understand why to use async instead. [0] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...

This is why I'm curious about algebraic effects which was recently discussed on /r/rust [0]

The main challenges I see are around usability within the language design on how best to propagate and compose them.

[0] https://www.reddit.com/r/rust/comments/cjcwmu/is_there_inter...

Re: Async-std: an async port of the Rust standard library

#96

Earlier quoted context omitted.

> that would have been hard to do 5 years ago. Five years ago Rust still had green threads. Literally every standard library I/O function was async, and the awaits were always written for you with no effort. Its literally taken five years to get back to an alpha thats not as good, and we'll still have to wait for a new ecosystem to built on top of it. I know not everyone writes socket servers and so forcing the old m…

> Its literally taken five years to get back to an alpha thats not as good The new I/O system is better in several ways. First, as you acknowledged, not everyone writes servers that need high scalability. M:N has no benefit for those users, and it severely complicates FFI. Second, async is faster than M:N because it compiles to a state machine: you don't have a bunch of big stacks around.

Yes, its better in several ways, but its also worse in several ways. It will take another five years to build a robust ecosystem for servers, and you'll still have to be careful not to import the wrong library or std module and accidentally block your scheduler. Plus the extra noise of .await? everywhere.

I'm not saying it was the wrong decision five years ago, but it definitely was a choice and there could have been a different one. I was responding to someone who said async wasn't an option five years ago.

Re: Async-std: an async port of the Rust standard library

#98

Earlier quoted context omitted.

That’s what I like about Go. You write sync code, but because Go routines aren’t OS threads they operate with the efficency of async code.

> You write sync code, but because Go routines aren’t OS threads they operate with the efficency of async code. No, they don't. Goroutines have stacks, while Rust async code does not. Go has to start stacks small and copy and grow them dynamically because it doesn't statically know how deep your call stack is going to get, while async/await compiles to a state machine, which allows for up-front allocation. Furthermor…

> OS threads are not significantly different from goroutines in terms of efficiency

This is not true for a use case with a lot of connections, additionally context switch cost a lot more now with all side channel attack mitigations on.

Re: Async-std: an async port of the Rust standard library

#100
post #92

Earlier quoted context omitted.

A good example is say you want to handle 100k TCP sessions concurrently. You probably don't want to launch 100k threads considering the overhead in doing so and constantly switching between them. You also don't want to do things synchronously as you'll constantly be waiting on pauses instead of doing work on the 100k sessions. So you launch 100k instances of it as an async function and they all stay in a single threa…

This is missing a crutial explanation that the underlying OS API are asynchronous.

Yeah for sure. In Java/C# I see people do this all the damn time. Use async method for REST endpoints then make a blocking DB call. Or even worse, make a non-async REST call to another service from inside an async handler.

As soon as you do that, your code isn't async anymore. And if you're using a framework like Vert.X or node that only runs one thread per core you're in big trouble.

The most reasonable answer I've seen to all this is Java's Project Loom. An attempt to make fibers transparently act like threads, so you can use regular threaded libraries as async code.

Rust is going to have the same problem Java does with async. A lot of code was written way before async was available, and it not always obvious whether something blocks.

Post reply on HN