But I do agree that it seems that people jump to use async instead of e.g. threads. I think there should be more literature around what situations work best for threads vs async, etc.
Then again, few programs are actually single-tasked in practice, and libraries often end up taking advantage of those async primitives for doing stuff like connection pooling even if your application code doesn't see it.
thing1_promise = get_thing(1)
thing2_promise = get_thing(2)
thing1 = thing1_promise.await
thing2 = thing1_promise.await
is almost always more difficult to do with threads. Sure, some threading APIs support joining nicely but it tends to be more awkward. Especially if you want to do something like handle the first request to finish first.Also the startup cost of threads affects how you write code. Does your library spawn a new thread for every HTTP request? Probably not, it is expensive and the server may be on the same host. So you end up with less consistent async support. Since async programming is a lot cheaper you tend to see better support which adds up.
The only way to run code in another Thread is by spawning an `Isolate`. But once you have one, you can expose an API that is indistinguishable from an usual async one.
Try `dart compile exe app.dart` and then run `./app.exe`... it's really fast and the binary is fairly small!
The installation process is simple, documentation is excellent, the core library makes sense, the defacto package repository and package management is clean. And WRT this thread, the async model is dead simple to pick up.
As a scripting language it seems to be a perfect fit. But there is also a lot of first rate tooling available for Dart and I am starting to wonder if it wouldn’t be a good choice for larger projects as well.
And the Dart 2.15 announcement (latest version as of today), which had a focus on making Isolates fast: https://medium.com/dartlang/dart-2-15-7e7a598e508a
EDIT: the main article about concurrency in Dart is actually this one: https://dart.dev/guides/language/concurrency
Thus you only pay for it if it is required.
Minimal-cost threading support requires taking Send + Sync into account, essentially thread-safety: and the thread-safe primitives are more expensive than the thread-local primitives. Send + Sync matters become pervasive through the design of threaded code, even if they don’t need to be spelled out most of the time.
Minimal-cost async requires compiling the code into a state machine, which means you’ve got to restrict borrowing across suspend points, or mess around with Pin.
Threading and async are quite different in a language like Rust. They can even play quite nicely together. But you really can’t unify them.
Sure, you could do simple cases like single-threaded blocking-or-async, e.g. `async? fn foo() { bar().await; }` compiling to something like strawman `fn sync#foo() { sync#bar(); } async fn async#foo() { async#bar().await; }`, but this falls apart completely as soon as you want to do anything beyond immediate .await.
I guess I'm glad that Rust developers don't design CPUs, as we wouldn't have memory abstractions like cache coherent NUMA (which doesn't come for free).
Case in point: a CPU plus a discrete GPU is technically a NUMA system, and low-level APIs like Vulkan explicitly expose the ability to allocate non-coherent memory. I strongly assume that accessing that memory doesn't incur coherency overhead.
What are you even talking about? Rust gives you plenty of choice, it even lets you create as many "non-free" abstractions as you'd like.
As another example: compare "GC'd versus non-GC'd memory in Rust" to "coherent versus non-coherent memory in CPU architecture"; here Rust gives us just the bare machine, while CPU architects give us quite some abstraction plus a choice.
It is true that you could theoretically write a GC in Rust, but using it from the language itself would probably break ergonomics so much that you'd be better off with another language.
The examples they give to use the API using async, I really don't understand, and I can only assume I'm missing something: the example steps of going to a URL, finding a resultant element, entering text, then clicking the search button, then finding another element and then capturing a .png snapshot image for the result are in my mind all sequential, and depend on the previous step: I really struggle to understand how async helps in this case (at least the basic one), as I don't see how any of those steps can be parallelised.
Classic WebDriver, when used to control a single browser is indeed a synchronous API. So for example the server-side WebDriver implementation used in geckodriver is fully synchonous; it accepts a HTTP request, and does a bunch of blocking work to execute that command in Firefox. But there are a couple of reasons you might want an async client, even though the browser side is synchronous:
* You might want to drive multiple browsers at the same time. For example when running multiple tests in parallel you would have multiple concurrent requests going to different browser instances. Obviously you could implement that as multiple threads/processes/etc. but if async has the ergonomics you're looking for it is certainly a viable option.
* Modern extensions to WebDriver don't follow the command/response model. Many clients have additional functionality that's not implemented on top of WebDriver, but using browsers' built in debugging protocol. Even Selenium these days uses the Chrome Debug Protocol for some features (e.g. access to logs). Those protocols are event based and so not suitable for a fully blocking API. Of course one could still design something that doesn't require async for those use cases, but I think it's generally believed that async offers the best APIs in languages that support it.
I don't know anything about webdriver's API specifically, but as a general example, "do something else while waiting for a response" can include "stop waiting for a response after a timeout expires". Maybe you want to set a timeout on the "fetch a URL" function call. You can't do that with a synchronous function unless the function internally supports a timeout. With an asynchronous function you can just race it with a timeout future, and if the timeout future completes first you can stop waiting for the fetch future and fail your test.
Async/await as concept taints the call stack all the way up to main and is unmanageble on existing code.
While working on C++/WinRT I am seeing the same problematic showing up, so no wonder that Rust ecosystem is now facing the same issues.
Also works better than callbacks for single threaded/event loop scenarios like JS or some GUI framework (with the caveat that you need to make sure you are resuming on the event loop if environment is multithreaded).
it pollutes every function with async annotation.
it allows you to quickly write the code imperatively by just annotating a function and using the async function inside it.
it results in code that is massively polluted with async when at most places you never had to use it at all (if you just restructured the code).
having async be as verbose as possible would enforce people to not use it as much and to structure their code to have async bits nicely separated from non-async bits.
Whatever the reason is for using async, I believe that the mismatch happens not because Async is optimized for I/O bound programs but because it's insanely easy to use (in Rust, Javascript).
Function is blocking and you want to use it in an async context without blocking the async worker thread? Use your runtime's equivalent of `spawn_blocking(|| sync_fn()).await` that will run the function call in a threadpool dedicated for blocking tasks.
(Both those examples are for tokio.)
There are no extra threads in the former case.
>(and in the second case, potentially undermine the whole point of using async at all)
If code is blocking for long periods of time, it shouldn't be running on the async worker thread in the first place. Marking such a function `async` would be wrong. If I write a tight loop that takes seconds to do its job, putting an `async` on the function is worse than not having it. Spawning such work in a different thread is correct. There's no "whole point of using async" that is being "undermined".
Right, you need to go right down and e.g. use a non-blocking I/O primitive instead of a blocking I/O primitive. Rust gives you no way to polymorphically do one or the other.
That's 'front end and back-end' ... probably the bulk of software right there.
A bunch of threads running 'compute bound tasks' is probably a special case.
Writing in Rust takes more engineering effort and is less agile. You can code yourself into a corner which requires considerable rewriting, or unsafe code, to escape.
What I'm writing in Rust is a metaverse client. These are not common yet. They have both the real-time problems of a game client and the unexpected content-driven loads of a web browser. The goal is to keep the frame rate constant as a stream of update transactions comes in, perhaps hundreds per second. The transactions modify the scene graph and change what's on screen. So refresh is in one high-priority thread, while updating is in other low priority threads. Overload bursts happen routinely and have to be dealt with, updating near and large things first. In MMOs, such overloads are usually detected and prevented during game design, but a metaverse viewer does not have that luxury. Existing clients tend to choke and stall on overload, but with enough CPUs working on the problem, that can be overcome.
It's a fun problem. It doesn't have the shape of most common applications.
But typical web stuff: Go or even Java would seem to be a much better choice than Rust, for the same reasons I would not write a typical web service in C or C++.
Depends on whether you are talking about a single machine or a distributed program.
A rendering library I use added an 'async' framework, but it turns out using it is not going to be mandatory. Much relieved.