[HN Gopher] Async2 - The .NET Runtime Async experiment concludes
___________________________________________________________________
Async2 - The .NET Runtime Async experiment concludes
Author : Nelkins
Score : 105 points
Date : 2024-08-22 11:52 UTC (5 hours ago)
(HTM) web link (steven-giesel.com)
(TXT) w3m dump (steven-giesel.com)
| xeromal wrote:
| It's a bummer green threads didn't work out
| giancarlostoro wrote:
| I still remember first hearing about Rust and how it was using
| Green Threads, then out of the blue a year later passes or
| months, and I'm reading that it doesn't do any of that anymore,
| it's basically C++ on steroids mixed with functional programming.
| I never did look up why they gave up on Green threads and other
| things, I wonder if they faced similar challenges?
| wyager wrote:
| Rust has settled into an excellent design niche where the
| language does not provide any features that require a language
| runtime.
|
| Rust's async/await is a purely compile-time feature, with no
| runtime support required.
|
| This is extremely powerful because it means you can do things
| like easily run async code on embedded devices with no OS.
|
| To run async code in rust, you bring your own executor. The
| most popular one is tokio (for desktop OSs), but you also have
| stuff like pollster (for running async code as blocking code)
| and embassy or RTIC (for embedded).
| jtrueb wrote:
| Agreed, as much as there are complaints about usability of
| async in Rust, many alternatives proposed would fail in
| resource constrained systems. Green threading would be
| impossible or very limiting on embedded, where each stack
| eats away at something like 1-2% of memory and allocation is
| likely impossible or prohibitively expensive.
|
| I do note that some of the futures are starting to take up a
| couple KiB of RAM and ROM.
| eknkc wrote:
| I might be completely wrong as I only played with rust but
| the current method seems to couple any async libraries you'd
| like to write to the executor. Which feels wrong imo.
|
| Would it be feasible to provide async io (file, network) code
| in std and let the executors execute?
|
| Or even some std traits that abstract the filesystem, network
| etc so that the executors can bring their own implementations
| but at least library code could only depend on std?
| wyager wrote:
| Correct, libraries do have to either pick a specific async
| IO library or use a trait for async IO.
|
| I have seen some of the latter in embedded-land, although
| I'm not sure if it's used in desktop async rust as well.
|
| The rust compiler currently discourages async traits with a
| warning, which seems wrong to me, but we use them
| extensively in embedded anyway.
| anonymoushn wrote:
| The feature is less than 1 year old, so a lot of the
| ecosystem couldn't use the feature because of being
| written before the feature existed. It does seem silly to
| have a warning today.
| anonymoushn wrote:
| This is a fine approach but it's trivial to ship green
| threads with no runtime support as well. Various libraries
| implementing the feature exist for C or Zig etc.
| dboreham wrote:
| User level (aka green) threading is difficult. Very few
| implementations work (Erlang, Golang, <any others>+) and it
| takes a great deal of time and effort to get to that "fully
| working" state.
|
| +Perhaps the new Java fiber stuff works but I don't have enough
| data to be sure yet.
| Ygg2 wrote:
| There were plans for green threads, however, green threads were
| abandoned due to runtime costs around like 6 years ago?
|
| https://github.com/rust-lang/rfcs/blob/master/text/0230-remo...
|
| EDIT: 10 years ago.
| steveklabnik wrote:
| It's coming up on ten years. Time flies.
| Ygg2 wrote:
| I thought it was 2018, it was 2014 instead.
| anonymoushn wrote:
| "green threads" as discussed in TFA don't require a runtime.
| The relevant issues are maybe https://github.com/rust-
| lang/rust/issues/33368 and https://internals.rust-
| lang.org/t/what-is-the-current-safety...
| steveklabnik wrote:
| https://github.com/rust-lang/rfcs/blob/master/text/0230-remo...
|
| Note this was in 2014, before Rust 1.0.
| hyutmyjahsd wrote:
| I miss peak rust.
| giancarlostoro wrote:
| Yes it was a while back, wow 10 years.
| jasode wrote:
| _> I still remember first hearing about Rust and how it was
| using Green Threads, [...] I never did look up why they gave up
| on Green threads_
|
| It's because green threads have unavoidable performance costs.
| I collected various threads that has Rust contributors
| explaining the costs they didn't want to pay:
| https://news.ycombinator.com/item?id=39062953
|
| A language like Go is willing to pay those performance costs
| because they deliberately sit at a higher level of abstraction
| than lower-level Rust.
|
| Each chose different tradeoffs for different goals:
|
| - Golang : green threads worth the perf cost to gain higher
| productivity (goroutines, channels, etc) for the type of
| programs that Go programmers like to write
|
| - Rust : green threads are not worth the perf cost so as to not
| sacrifice the best possible performance for the type of
| programs Rust programmers like to write
|
| You can't have a language+runtime that satisfies both camps
| 100% perfectly. The language designer _must choose the
| tradeoff_. But because this tradeoff decision is not blatantly
| well-known and disseminated ... it also perpetually confuses
| Language X proponents on why Language Y didn 't do the same
| thing as Language X.
| 01HNNWZ0MV43FF wrote:
| What do Go channels do that takes advantage of their green
| threads?
| giancarlostoro wrote:
| Thank you for this! This is exactly why I posted my comment,
| I was hoping someone would share some insight about Rust.
| zamalek wrote:
| Rust can have green threads. It depends on the reactor. The
| abstraction sits one level lower than nearly everything else
| out there.
| cube2222 wrote:
| This all makes sense but
|
| > Green threads are different. The memory of a green thread is
| allocated on the heap. But all of this comes with a cost: As they
| aren't managed by the OS, they can't take advantage of multiple
| cores inherently. But for I/O-bound operations, they are a good
| fit.
|
| this is clearly not true? Am I missing some nuance here, as I'm
| sure the author knows what they're talking about?
|
| Green threads can totally use a multi-threaded runtime, like e.g.
| Go does, and it works just fine. The main hurdle with them is
| arguably FFI.
| DougBTX wrote:
| The "inherently" means not by default, i.e., the runtime has to
| support moving green threads between OS threads itself.
| cube2222 wrote:
| Ah, that makes sense, thanks!
| 01HNNWZ0MV43FF wrote:
| That's not how I use "inherent", maybe they should just say
| "default" then?
|
| But that's like... C is also single threaded by default, what
| isn't?
| jayd16 wrote:
| They will never be transparently/fundamentalally managed by
| the OS alone. The runtime will need to determine how to
| juggle green threads across multiple OS threads. In that
| way, this mapping is not inherent.
|
| It can be designed around but that itself is a runtime
| design decision and I would not say it's akin to default vs
| custom.
| recursive wrote:
| With respect, it's not particularly relevant how you use
| "inherent". It's a standard usage. Rather than asking the
| whole rest of the world to change, you should probably
| learn the definition.
| layer8 wrote:
| "Inherently" means "intrinsically", meaning it's a
| characteristic that can't be changed without changing the
| nature of the thing. It doesn't mean "by default".
| louthy wrote:
| Presumably, it just means there needs to be _explicit_ forking
| of the green thread for cpu bound operations, otherwise
| everything will run synchronously (because there's no point
| where the green thread is paused to wait for an IO IRQ).
|
| That is unless your compiler or JIT injects occasional yields
| into your synchronous code!
| Rohansi wrote:
| And that wouldn't be great for performance.
| 01HNNWZ0MV43FF wrote:
| The overhead for epoch stopping like wasm uses can be
| something like 1%. I did a synthetic test with native code
| once because I was curious.
|
| I think Go also injects yields into its generated code for
| go routine scheduling
| adgjlsfhk1 wrote:
| Jits already have to do this for GC so it's actually free
| bilekas wrote:
| There are nuances with multi-threading in C#.
|
| I don't agree with OP about I/O-bound ops, I think if you're
| looking to green threading, you've taken a wrong approach.
|
| > [0] the Task.Runmethod offloads the provided action to the
| thread pool, and the await keyword yields control back to the
| caller until the task completes.
|
| All async code must be in an async call stack, virtual threads
| are 100% transparent because its the runtime scheduling them so
| you get a but more control than relying on the yeild of dotnet
| at least as I see it.
|
| Again I don't see the huge demand for it personally, but I
| barely touch dotnet too often so take this with a grain of
| salt.
|
| [0] https://stackify.com/c-threading-and-multithreading-a-
| guide-...
| arghwhat wrote:
| > I don't agree with OP about I/O-bound ops, I think if
| you're looking to green threading, you've taken a wrong
|
| It depends in the implementation. In Go for example, all I/O
| is async and suspend your green thread, replacing it with
| another runnable green thread.
|
| This works the same as if you managed an event loop on your
| own for the purpose of I/O, which is the best way to handle
| I/O outside for regular user space code. It's just automatic
| with your code resembling a simple, blocking scenario.
|
| OPs note on threading would be C# or runtime specific - green
| threads have no problem with parallelism, with runtimes
| commonly having a thread per core (or more) and having them
| all run green threads in parallel.
| neonsunset wrote:
| What this likely means is for you to take advantage of the
| underlying runtime multiplexing green threads over multiple
| physical ones running on multiple cores, you need to explicitly
| fork the execution flow.
|
| This could be as simple as a web server firing off a new green
| thread or a goroutine for an incoming request, or as contrived
| as doing so manually within a function scope.
|
| In practice, there really is not much difference with
| async/await. "Green threads" is a combination of implementation
| details and a subset of what async/await abstractions achieve.
|
| Effectively, Goroutines are in many ways similar to C#
| Task<T>s. The difference is that in Go you are expected to
| explicitly send the result via a channel or some other data
| structure and then synchronize the completion of the execution,
| where-as with tasks you simply await that.
|
| There could be an argument made about preference of implicit
| suspend (Go, Java, BEAM family) over explicit suspend (C#/F#,
| Rust, JS, Python, C++ co_await, Swift), but for practical
| purposes invoking a function with 'go' keyword in Golang is
| very similar to firing off a synchronous method with Task.Run
| in C#, or calling an asynchronous method (with sufficiently
| short body before first yield) and _not_ immediately awaiting
| it.
|
| As I usually post it on HN, tasks make the following patterns
| trivial: using var http = new HttpClient {
| BaseAddress = new("https://news.ycombinator.com/") };
| // not immediately awaited requests are executed in parallel
| var frontPage = http.GetStringAsync("news?p=1"); var
| secondPage = http.GetStringAsync("news?p=2");
| Console.WriteLine($"{await frontPage}\n\n{await secondPage}");
| spinningslate wrote:
| > The difference is that in Go you are expected to explicitly
| send the result via a channel or some other data structure
| and then synchronize the completion of the execution, where-
| as with tasks you simply await that.
|
| That may be be the case in Go but it's not an inherent
| property of green threads. See, for example, Gleam Tasks [0]
| which are based on green threads and provide the syntatic
| convenience of being able to await the result rather than
| receiving a message: let task =
| task.async(fn() { do_some_work() }) let value =
| do_some_other_work() value + task.await(task, 100)
|
| They do so without the disadvantage of bifurcating the code
| base into sync and async functions.
|
| [0] https://hexdocs.pm/gleam_otp/gleam/otp/task.html
| neonsunset wrote:
| Of course.
|
| The discussion regarding Goroutines is to highlight that,
| despite prevalent claims of otherwise, they are not doing
| something unique and for developers who are used to
| languages with powerful concurrency primitives look like an
| incomplete task abstraction. "Green Threads" really is an
| implementation detail, in many ways orthogonal to pros/cons
| of implicit and explicit suspend points.
|
| I hope your opinion about C#'s task system has improved
| since the last time[0], given what Gleam (and, in many
| ways, Elixir) does looks practically identical :)
|
| [0]: https://news.ycombinator.com/item?id=40427935
| pron wrote:
| The efficiency and complexity of user mode threads heavily
| depend on constraints imposed by the particular language. E.g.
| if the language supports pointers into the stack, user mode
| threads would be less efficient; if the language is largely
| dependent on manual memory management -- user mode threads
| would be more expensive; if the language already has some other
| concurrency primitives (like async/await) -- user mode threads
| will be more expensive (although in this case in terms of
| complexity rather than runtime efficiency). Because Java
| exposes relatively little of its implementation details, we've
| been able to implement efficient user mode threads even without
| any FFI overhead.
| bob1029 wrote:
| I don't know if this proposal makes a lot of sense.
|
| The existing async1/TPL path is stable & predictable. If you find
| yourself needing more performance, you can reach for hardware
| Thread instances and use whatever locking/waiting/sharing/context
| strategies you desire. Anything else is a weird blend of
| compromises that is going to have caveats that are not
| immediately obvious for your specific use case.
|
| For example, async2 w/ runtime JIT appears to have some tradeoffs
| with regard to GC & memory usage and the experiment writeup
| leaves some open-ended questions here[0].
|
| [0]:
| https://github.com/dotnet/runtimelab/blob/feature/async2-exp...
| whaleofatw2022 wrote:
| Trying to do clever stuff with threads tends to lead from brow
| beating, wailing, and gnashing of teeth by various folks who
| insist to just trust the threadpool.
| GordonS wrote:
| > If you find yourself needing more performance, you can reach
| for hardware Thread instances
|
| True for code your team write, but async/await is kind of
| viral, and many libraries now only have async APIs, which makes
| them difficult to shoehorn into a threading approach.
| bob1029 wrote:
| > async/await is kind of viral
|
| Exactly part of my concern. Hypothetically we've got 2
| competing viruses now.
| neonsunset wrote:
| For everyone reading this blog post I caution to take the
| conclusions there with a grain of salt as they are an
| interpretation of the notes written down here:
| https://github.com/dotnet/runtimelab/blob/feature/async2-exp...
|
| It is difficult to draw conclusions at the present moment on e.g.
| memory consumption until the work on this, which is underway,
| makes it into mainline runtime. It's important to understand that
| the experiment was first and foremost a research to look into
| modernizing async implementation, and was a massive success. Now
| once that is proven, the tuned and polished implementation will
| be made.
|
| Once it is done and makes into a release (it could even be as
| early as .NET 10), then further review will be possible.
|
| With that said, thank you for writing it, .NET tends to be
| criminally underrated and slept on by the most of the industry,
| and these help to alleviate it even if just a bit.
| hirvi74 wrote:
| > .NET tends to be criminally underrated and slept on by the
| most of the industry
|
| I have been programming in .Net/C# for about 8 years now. I
| absolutely hate Microsoft with every fiber of my being.
| However, I can't thank them enough for C# (and all the FOSS
| contributors as well). .Net has been such a pleasant experience
| that I truly do not want to program in any other language.
| naasking wrote:
| > I absolutely hate Microsoft with every fiber of my being.
|
| Why? I get it back when they were super hostile to Linux and
| open source, but those days are long past.
| anonymoushn wrote:
| Maybe because GP uses Windows sometimes.
| orphea wrote:
| They are super hostile to users of their OS, and these days
| are right now.
| geodel wrote:
| Absolutely. It is just sad experience using Windows
| beyond some web browsing with Non-MS browser.
| MangoCoffee wrote:
| >hostile to Linux and open source, but those days are long
| past
|
| Microsoft remains a giant elephant with questionable
| business practices. Their shift to SaaS and cloud computing
| meant they have to play nice with others.
| runevault wrote:
| I see people complain sometimes that c# adds too many features,
| and I understand the concern, but with limited exceptions (I
| don't like the new global using stuff), they tend to feel
| tasteful to me. Also I'm really hopeful we'll get Discriminated
| Unions (under a name I'm forgetting) in c# 10.
| ikekkdcjkfke wrote:
| What is the fundemental problem we are trying to solve here? My
| pet problem with async is that it takes so much syntax, and i
| wonder why we need to do that. As i understand it it is all about
| blocking calls taking up OS threads.
|
| So i will try to naievly solve it here, and maybe i end up with
| the same conclusions.
|
| When doing a blocking call, the OS thread could just start
| executing something else transparently and not 'yield' anything
| to the code, it just stops executing the code and comes back and
| executes further, no await, no tasks no nothing. Ok, but how did
| we end up in a thread? The http listener started 4 threads and
| it's just putting a stack of function pointers and context memory
| for the threads to eat when they want. There is a separate engine
| on another OS thread that handles where the threads can start
| executing ready code again. No Task or await keywords show up in
| this code. I have no idea how stack traces work but i guess that
| can just be saved to memory and loaded back again when the
| threads feel like executing some ready code
| irdc wrote:
| The rationale for async/await I keep hearing is that the
| separate stack required for each thread uses a lot of memory.
| I'm not sure going full on function colouring is the answer
| though. A concerted effort for language runtimes to use less
| stack space and then simply allocating smaller thread stacks
| sounds to me like a more elegant solution. It certainly is a
| whole lot easier to debug than a deeply-nested async/await
| chain.
| iknowstuff wrote:
| But also avoiding the cost of context switching, and the
| ability to handle many tasks in embedded environments.
|
| https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown
| layer8 wrote:
| What you propose is similar to how Java virtual threads work.
| jayd16 wrote:
| Ok well OS threads are already scheduled this way... The
| drawback is they're heavier because they make assumptions that
| the runtime doesn't need to make.
|
| Furthermore, explicit Async/await provides a syntax for co-
| opoerative multithreading and that enables other patterns that
| implicit designs don't.
| kodablah wrote:
| > And for the transition phase, there has to be interop for async
| - async2
|
| For those like me who weren't clear whether `async2` was expected
| to be a real keyword in the final language, it's not[0].
|
| 0 -
| https://github.com/dotnet/runtime/issues/94620#issuecomment-...
| augusto-moura wrote:
| async2 looks like such a terrible keyword name, that it didn't
| even crossed my mind as an option. Only following your linked
| comment I understood that they used it as a temporary name
| while testing
___________________________________________________________________
(page generated 2024-08-22 17:01 UTC)