9 ms·
I can see you having objections to their arguments against async/await, but what makes you say async/await is somehow the better solution?
by wtetzner 4y ago
I can see you having objections to their arguments against async/await, but what makes you say async/await is somehow the better solution?
- mgraczyk 4y agoThere are a few reasons. async/await allows you to do multiple things in parallel. I don't see how you can do that in the virtual threading model, although I haven't used it and only read this article. You would have to spin up threads and wait for them to finish, which IMO is much more complicated and hard to read. javascript async function doTwoThings() { await Promise.all([ doThingOne(), doThingTwo(), ]); } python async def do_two_things() { await asyncio.gather( do_thing_one(), do_thing_two(), ); } Another issue is building abstractions on top of this. For example how do you implement "debounce" using virtual threads? You end up unnaturally reimplementing async/await anyway. Finally it's generally much easier implement new libraries with a promise/future based async/await system than with a system based on threads, but I'm not familiar enough with Java to know whether this is actually a good objection. It's possible they make it really easy.
- Matthias247 4y agoNote that does do not natively reflect structured concurrency - and a Promise.any version does even less so. The code above would „return“ once the first Promise runs to completion. The computation that is described by the second Promise would however continue to run - which could cause unexpected side effects. The goal of structured concurrency is to totally avoid those by making sure the lifetime of child tasks is always contained within the lifetime of their parent tasks. Bare async/await only marginally guarantees that, and the amount depends on the language. Eg the JS code above will never guarantee it, since each async function is really a separate entity on the eventloop. Rust async/await can do it if you all use are low level future combinators (like futures::join! or select!. But if the code spawns any child tasks then all bets are off too. The Java example with scoped executor will have proper guarantees. Waiting on the executor means waiting for all tasks to complete. And as soon as the first task errors other tasks are „asked“ to stop early via an InterruptedException.
- mgraczyk 4y agoAh thanks, I didn't think of it like that. I've never used actual structured concurrency in JavaScript or Rust, but I python I have worked on codebases that do this. Seems increasingly common. It seems like most of the time you don't want or need the full flexibility of async/await, but I don't think the guaranteed structure is worth the benefits to me if the language doesn't support it natively. Too much boilerplate, and static analysis is usually pretty good about catching mistakes, in python at least.
- Jtsummers 4y ago> async/await allows you to do multiple things in parallel. I don't see how you can do that in the virtual threading model, although I haven't used it and only read this article. The description of this is that the virtual threads can move between platform threads, quoting from the article: > The operating system only knows about platform threads, which remain the unit of scheduling. To run code in a virtual thread, the Java runtime arranges for it to run by mounting it on some platform thread, called a carrier thread. Mounting a virtual thread means temporarily copying the needed stack frames from the heap to the stack of the carrier thread, and borrowing the carriers stack while it is mounted. > When code running in a virtual thread would otherwise block for IO, locking, or other resource availability, it can be unmounted from the carrier thread, and any modified stack frames copied are back to the heap, freeing the carrier thread for something else (such as running another virtual thread.) Nearly all blocking points in the JDK have been adapted so that when encountering a blocking operation on a virtual thread, the virtual thread is unmounted from its carrier instead of blocking. This allows for parallelism so long as the system is multicore and the JVM has access to multiple parallel threads to distribute the virtual threads across.
- mgraczyk 4y agoTwo separate threads run in parallel, but one thread cannot do two subtasks in parallel without submitting parallel jobs to an executor or a StructuredTaskScope subtask manager. It's basically forcing the developer to do all the hard work and boilerplate that async/await saves you.
- pron 4y ago> It's basically forcing the developer to do all the hard work and boilerplate that async/await saves you. It doesn't. Both require the exact same kind of invocation by the user. Neither automatically parallelises operations that aren't explicitly marked for parallelisation.
- spullara 4y agoThe only difference between how virtual threads work and how async/await work is that you don't need to use await and don't need to declare async. Just call .get() on a Future when you need a value - that is basically "await". void doTwoThings() { var f1 = doThingOne(); var f2 = doThingTwo(); var thingOne = f1.get(); var thingTwo = f2.get(); }
- mgraczyk 4y agoHow do you implement doThingOne? You should read the "structured concurrency" link in the article. You have to explicitly wrap the call to doThingOne in a future under a structured concurrency scope. The code example you wrote is not going to be possible in Java without implementing doThingOne in a complicated way.
- spullara 4y agoYou don't have to use structured concurrency. The two methods use a virtual thread executor and return the result of submitting work. Structured concurrency is great but not necessary. https://www.reactivesystems.eu/2022/06/17/introduction-to-project-loom.html https://www.reactivesystems.eu/2022/06/17/introduction-to-pr...
- merb 4y agogreat, now you have futures AND virtual threads. soo much better!
- gpderetta 4y agoIt is significantly better. The issue with futures is chaining them together and having to thread continuations by hand. Async/await is an improvement over that, but when you can just block instead they become much nicer to use.
- kagakuninja 4y agoJava Futures are lobotomized. What is really nice is to treat Futures as monads (as in Scala, and FP languages like Haskell). Then you can chain them together using calls to flatMap aka bind. Then you can use for-comprehensions (or the equivalent): for { a <- taskA b <- taskB c <- taskC } yield a + b + c There are a ton of other fancy tricks found in Haskell or libraries such as Cats.
- e63f67dd-065b 4y ago> async/await allows you to do multiple things in parallel. I don't see how you can do that in the virtual threading model, although I haven't used it and only read this article. You would have to spin up threads and wait for them to finish, which IMO is much more complicated and hard to read I think there's a fundamental point of confusion here. In both python and JS, you can't do anything in parallel, since node/v8 and cpython are single-threaded (yes if you dip down into C you can spawn threads to your heart's content). You can only do them concurrently, since only when a virtual thread blocks can you move on and schedule another thread in your runtime. In c++ (idk the java syntax, imagine these are runtime threads): std::thread t1(doThingOne, arg1); std::thread t2(doThingTwo, arg2); t1.join(); t2.join(); // boost has a join_all I'm sure there's some kind of `join_all` function in Java somewhere. Imo this is even more clear than your async await example: we have a main thread, it spawns two children, and then waits until they're done before proceeding. The traditional problem with async/await is that it forces a "are your functions red or blue" decision up-front (see classic essay https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...). > Finally it's generally much easier implement new libraries with a promise/future based async/await system than with a system based on threads How so? Having written a bunch of libraries myself, I have to say that not worrying about marking functions as async or not is a great boon to development. Just let the runtime handle it.
- mgraczyk 4y agoThe first part is semantics, yes I understand that python is running one OS thread at a time with a GIL (for now). Just pretend I used the word "concurrent" instead of parallel in all the places necessary to remove the semantic disagreement. Whether threads and joining vs async/await is clearer is a matter of taste and familiarity. I find async/await much more clear because that's what I am more used to. Others will disagree, that's fine. I suspect more people will prefer async/await as time goes on but that's my opinion. > not worrying about marking functions as async or not is a great boon to development. I don't really see why this is a big deal. You can change the function and callers can change their callsite. There are automated lint steps for this in python and javascript that I use all the time. It's not any different to me than adding an argument or changing a function name.
- Scarbutt 4y agoasync/await allows you to do multiple things in parallel. I don't see how you can do that in the virtual threading model When comparing to JS, it is the other way around. Unless you are talking about IO bound tasks only where nodejs delegates to a thread pool (libuv).
- mgraczyk 4y agoWith virtual threads, you need to write fork/join code to do two subtasks. With async await, you call two async functions and await them. So the virtual threading model ends up requiring something that looks like a worse version of async await to me.
- anonymoushn 4y agoIn Python you won't get any concurrency by calling two async functions then awaiting each of them, but in Javascript you will.
- Jtsummers 4y agot1 = async(Task1) t2 = async(Task2) await t1 await t2 t1 = fork(Task1) t2 = fork(Task2) t1.join() t2.join() What's the difference?
- mgraczyk 4y agoIf Java adds some nice standardized helpers like this, they will look equivalent. The current proposal is not this clean but that doesn't mean it won't be possible. The key difference is that async/await implies cooperative multitasking. Nothing else happens on the thread until you call await. I find that an easier model to think about, and I opt into multithreading when I need it. Anyway Rust does this roughly using roughly the syntax you described (except no need to call "fork"). Languages that use async/await do not require you to say "async" at the call site.
- wtetzner 4y ago> Languages that use async/await do not require you to say "async" at the call site. Nope, but if you start calling an async function from within a non-async function, then that function now has to become async, and all of the callers of that function have to become async. This isn't a problem with the green threads approach.