12 ms·
How Async/Await Works in C#
- tester756 4y agoStephen's posts exciting as always!
- oaiey 4y agoI admire his work. He takes the time to deep dive us in such a variety of topics. Just awesome.
- serguzest 4y agoI have the conclusion opt-in async/await is a mistake. I think high level language/runtime like c# should handle it automatically like it is a sync call but a way to opt-out should be still possible. In my career, 99% of the time my code awaited async calls. I remember one case we tried to run tasks parallel, but the client for DB(ravendb) started throwing exceptions, possible race condition. Also, they did terrible work when it was first introduced, low level details were leaked to app surface and it was impossible to comprehend for a regular programmer what the hell is going on? Do you remember ConfigureAwait(false)? Years later such guidance compiled: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/b...
- valenterry 4y agoNope, that's a fallacy. Sync and Async code have inherently different semantics and trying to unify them will always fail, no matter what technique you try to apply. It has been tried more than often enough.
- unxdfa 4y agoCompletely agree with this. Having ported a lot of old non async c# to async, even mixing the notions for a moment is a disaster. This was a world of pain in the late .net classic lifecycle (4.5-4.8) where Microsoft kept fucking it up as well as us.
- foepys 4y agoIf you have legacy or UI code you are best served using Microsoft.VisualStudio.Threading to ease the pain. Not using it will land you in a world of random deadlocks and disappearing data from variables. vs-threading should be part of the .NET standard library but somehow isn't.
- pjmlp 4y agoHello Task.Run() scattered all over the place.
- unxdfa 4y agoI cried a little when I read this :(
- cube2222 4y agoGo did that, and it's working out very well. Agree with the grandparent. Async/await is unnecessary complexity in all but the most performance-oriented languages (like Rust). Very curious to see how Java Loom will pan out in practice.
- pjmlp 4y agoJava has a better approach, because you can still decide which kind of thread to use, which makes FFI performance still much better than cgo.
- oaiey 4y agoOnce you develop in a non single threaded ui framework it is not only about performance.
- cube2222 4y agoCould you please expand on that?
- oaiey 4y agoYou need to keep the UI responsive, so you hand off all resource or cpu heavy work off the UI thread, give back control to rendering and responding to user input ... and later need to collect the results in the UI thread to change the UI state. It is about doing things in parallel. There are many ways on how to achieve that, I am just nit picking on performance.
- cube2222 4y agoThen I think we misunderstood each-other. Go is asynchronous-first, it just doesn't use the (flawed) async-await semantic to model asynchronicity.
- metaltyphoon 4y agoNot really, look at doing FFI using Go. Its very slow compared to C# specially because of how Goroutines work.
- he0001 4y agoI think that’s one of the reasons why Java went with virtual threads instead, as the Async/await pattern is hard to do? As that does give you asynchronous code without any of the coding hustle and hide it from the programmer and you can keep your APIs the same.
- throwawaymaths 4y ago> trying to unify them will always fail Wrong. Languages that definitely got it right: Erlang/Elixir (high level), especially elixir's Task.async Zig (low level) Very close runner-up: Go
- valenterry 4y agoErm, just look at the name: "Task.async". So there is the distinct concept auf async, otherwise why would you name it like that. Mind that I'm not talking about async/await syntax here.
- SirGiggles 4y agoNo there isn’t. Elixir, and by extension Erlang and the BEAM VM, doesn’t have the concept of stackless coroutines. Task.async/1 (or Task.async/3) at a high level just spawns another BEAM process links it to the currently running process, sets up a process monitor, and then runs. Task.await/2 just pulls from the mailbox of the current process for a returned message.
- valenterry 4y agoBut that is exactly what I mean. There is a semantic difference between just running a line of code or running ith with Task.async right? So then there is a distinct concept of sync vs async.
- SirGiggles 4y agoThe BEAM VM doesn't have faculties to accommodate any other form of concurrency other than synchronous, pre-empted green threads. Put simply, there is nothing other than synchronous in Elixir (edit: or Erlang or any other BEAM VM language). The Task module is basically just convenience functions (edit: convenience functions around existing BEAM VM functions for spawning BEAM processes or Erlang threads) meant to facilitate short term execution of, well, tasks. Task.async/3 and Task.await/2 are, from the thousand foot view, the equivalent of: parent = self() spawn(fn -> send(parent, do_something()) end) receive do result -> IO.puts result end In Elixir/Erlang parlance, I have just spawned a thread where the BEAM VM handles both the internal data structures to facilitate concurrent-safe communications between threads and the scheduling in userspace. Put more generally, the mentality of concurrency in Elixir/Erlang is the equivalent as that of traditional pthreads in C.
- pjmlp 4y agoYet David Fowler has been researching how to integrate Go and Java's approach into .NET.
- pron 4y agoCan you try and express what the semantic differences are exactly? Most approaches for expressing semantics yield equivalent results or nearly so. For example, using a Hoare triple approach, what conditions in P and Q would yield different assertion results between, assert P; foo(); // blocking call assert Q; and assert P; await foo(); // await on an async call assert Q; ? There may be some (e.g. thread identity is preserved in the former but possibly not in the latter, depending on implementation, and that could be expressed with proper assertions), but they only highlight how the differences are merely technical and certainly not "inherent".
- valenterry 4y agoIn your examples you are explicitly handling the async nature of foo(). So you acknowedlge that there is a difference and you even use specific syntax to deal with it. The question is rather: is it possible to remove the await and just automatically rewrite every program to use await where necessary? And the answer is no, because with only sync functions you have the guarantee that if your thread crashes, every code immediately stops executing. Not so with async functions. Once they are run there is a chance that your own thread crashes but some other thread keeps executing more code, at least for some time, potentially executing side effects. It cannot be automatically decided if this is okay or not. Or to turn it around: the behaviour of a program that automatically awaits async functions (which formerly where sync) can be different.
- pron 4y agoI think we're talking past each other. There is a semantic difference between `asyncFoo()` and `blockingFoo()`, but not between `await asyncFoo()` and `blockingFoo()`, and not between `asyncFoo()` and `spawn blockingFoo()`. There's obviously a difference between launching an operation to run concurrently with the spawning code and launching an operation and awaiting its completion, but that difference does not require the introduction of an async/await feature, and is perfectly expressible with nothing but threads and synchronous code. In particular, there is no need for two separate syntactic domains between async and sync subroutines. Indeed, in Erlang, Go, and Java, all the same behaviour of async/await is achieved with no such need, and no need for different kinds of APIs.
- serguzest 4y agoYou should elaborate. I just want to read a file, program flow should stop until it is done. sync or awaited async call for this semantically sounds exactly same to me. I knew Go can do it, so it doesn't "always fail"
- valenterry 4y ago> You should elaborate. I just want to read a file, program flow should stop until it is done. sync or awaited async call for this semantically sounds exactly same to me. What if you are reading the file and somewhere else in the code there is some logging happening. Should the logging flow stop until reading the file is done? Should it not? If not, what should happen if the "thing" (e.g. thread) that executes the code to read the file crashes? Go does not treat sync and async the same. It specifically has syntax to deal with async, e.g. go routines.
- cm2187 4y agoFor desktop apps, you have to design the UI completely differently if it async or blocking. If it is async then suddenly the user can push other buttons while the previous action is executing which makes it a magnitude more complex.
- quickthrower2 4y agoYou never want to block your UI thread for any amount of time a user would notice say more than 20ms. And if you use async or another thread you can always update the UI to disabled with spinner state. This is all less convenient for the programmer but much better for the UX. Blocking the UI blocks it all, even the close button!
- SideburnsOfDoom 4y ago> I think high level language/runtime should handle it automatically like it is a sync call I understand that such a design is called "green threads" https://en.wikipedia.org/wiki/Green_thread https://en.wikipedia.org/wiki/Green_thread As far as I know, there have been recent prototypes in .NET, but nothing shippable. https://stackoverflow.com/questions/37285366/green-threads-in-net https://stackoverflow.com/questions/37285366/green-threads-i... https://www.reddit.com/r/csharp/comments/vapgxn/net_experiments_with_green_threads/ https://www.reddit.com/r/csharp/comments/vapgxn/net_experime...
- MezzoDelCammin 4y agoOh, thanks for the reminder. ConfigureAwait still lingers in a few of my libraries. It took me quite a bit of coffee to fully appreciate the simple phrase "it's async/await all the way down"
- quietbritishjim 4y ago> Do you remember ConfigureAwait(false)? What makes you think that this no longer applies? It still does to my knowledge. The page you linked to has a section for it that just says "TBD".
- serguzest 4y agoI actually have no idea if it still applies, but I haven't seen code that uses it for a long time. Maybe it was important for desktop apps?
- avsteele 4y agoIt's important to have ConfigureAwait(true) (the default) any time you need the continuation to be on the same thread. This is used/needed for many UI frameworks (WPF), which is why it is the default. Otherwise you should use (false) for performance reasons.
- foepys 4y agoYes, this is very important. I experienced deadlocks in ASP.NET Core 2.1 when I connected a legacy application to the web with it and the developers of Microsoft.AspNetCore.Mvc.Formatters.Json forgot to add .ConfigureAwait(false) to a async call into Newtonsoft.Json. This was easily fixed by writing a small warper around the JsonInputFormatter but it shows how important ConfigureAwait is. When writing a library for anything netstandard, always put ConfigureAwait(false) after each async call where it is not mandatory to return to the same thread again.
- SideburnsOfDoom 4y ago> It's important to have ConfigureAwait(true) ... Otherwise you should use (false) This is not universal. In modern ASP.NET core (i.e. web services), ConfigureAwait does nothing and is not recommended. I can confidently say that I don't want it in any of the code that I work on. We can make blanket statements, but clearly we do different things using different patterns and one size does not fit all. https://stackoverflow.com/questions/42053135/configureawaitfalse-relevant-in-asp-net-core https://stackoverflow.com/questions/42053135/configureawaitf...
- mandeepj 4y ago> ConfigureAwait(false) It’s literally the configuration, to be used according to your needs
- chrisoverzero 4y ago> Do you remember ConfigureAwait(false)? I remember it and still use it, and you should, too. It can be elided in ASP.NET Core, which is the web application framework. Outside of that, there’s no escaping that you have to know what you’re doing.
- recursive 4y agoIt's this type of stuff that causes me to prefer the Sync implementations over the Async wherever I can. Did I get it right? Is it going to deadlock in production? How can I be sure? Who knows? Straight threads are much easier for me to understand than async in C#.
- nottathrowaway3 4y ago> I have the conclusion opt-in async/await is a mistake. I think high level language/runtime like c# should handle it automatically... This is not a good idea unless you like having to make all your code thread-safe (well, async-safe). Explicit declaration of "hey this method might not run all at once or be interleaved with other methods" is a good idea; explicit opt-in is the same in every other implementation of async/await (ex: js).
- serguzest 4y agoI don't understand. Async methods can call sync methods already, how it is not mixed already? Nope, it is not same in every other implementation. e.g Golang.
- avsteele 4y agoasync/await are great: * pull based -> you don't need to worry so much about rate-limiting like you do with events * works well when needing to pull over slow communications (serial, remotes)
- pjmlp 4y agoUntil one needs to integrate a non async aware library and then is Task.Run() all over the place.
- MrGilbert 4y agoA thin abstraction layer could help.
- pjmlp 4y agoIt hardly works well across all use cases and consulting projects.
- martinald 4y agoPretty rare these days though.
- pjmlp 4y agoThese days there are still ton of libraries that are yet to run on .NET Core reboot, let alone fix their async/await story. Welcome to Python 2 / Python 3 version of .NET evolution.
- martinald 4y agoHmm not sure it's really anything as bad as Python2/3. I haven't came across a library that doesn't support netstandard (and 99% of those will do async/await) in a long time now. There are a lot of libs that haven't been updated for years and therefore don't support netstandard. But you probably don't want to be using them outside of very specific use cases given they are likely to be insecure by now. There definitely isn't a 'we're holding on to netframework' vibe in dotnet, like there was for python2.
- nottathrowaway3 4y agotl;dr > language and compiler capable of rewriting normal control flow into a state machine implementation of coroutines
- deleted 4y ago[deleted]
- Dzugaru 4y agoAsync/await in C# is a leaky abstraction though. There is a great writeup on this here https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
- VWWHFSfQ 4y agoYeah in my opinion it's one of the worst implementations of async runtime. Similarly with Python. It's not significantly faster, it can often times actually be slower, and the moment you write the async or await keywords in your code you've just cut yourself off from about 80% of what the language ecosystem has to offer.
- ycuser2 4y agoI don't think that async/await is about to make things faster. It's about to run things things in parallel. For example, you can run long tasks with making your GUI unresponsive.
- roflyear 4y agoYeah it depends on what you're doing. If you're doing a lot of IO probably makes sense. Not running long tasks tho. I don't think that's a good use in Python at least.
- mpweiher 4y ago1. Running things in parallel is always about making things faster. (For example, putting a long-running task on a background thread is about making the main thread faster). 2. We already have a way of making things run in parallel: threads and processes. See the first code example in the fine article. The OS
- Someone1234 4y agoI feel like I could untangle some this, but not without re-writing a version of the article that this thread is about. Like async/await adding overhead and therefore being slower than direct calls, if you use them incorrectly, is actually accurate but not a legitimate problem (they make up for their overhead when used as designed, specifically on slow endpoints). Also, async/await doesn't cut you off from anything. You just need to understand what is and isn't an asynchronousable end-point, people get confused then start throwing random keywords around without really grasping how it works (e.g. "if I await everything, it is concurrent, right?!" then write a bunch of code that actually runs synchronously but with tons of overhead). I guess this is my nice way of saying: You need to read this very article. It corrects your concerns, and shows you when async/await is useful and likely when it isn't.
- Waterluvian 4y agoI got rabbit holed by the first sentence. I use .NET so sparingly that I can’t seem to ever develop a full grokkin’ grasp of what the heck “.NET” is at any given time. It’s not .NET Framework anymore. Or .NET Core. And it’s none of the countless random things Microsoft has labelled .NET. I think the name fundamentally misleads my brain every time given it’s not necessarily a web framework. I need .NET runtimes for my video games. So it’s basically a managed group of tools and libraries to let you build stuff in a handful of different languages, usually C#, but targeting the same runtime, which is a JIT? So is it basically like Java but with far too much marketing involvement over the years?
- bazoom42 4y agoYes, in practice it is Microsofts Java: A virtual machine, a set of libraries, and a set of languages (C#, VB.net, F#).
- tester756 4y ago.NET is an ecosystem, an ecosystem made of languages, compilers, runtimes, libraries, tools and community.
- thatjoeoverthr 4y agoThis reminds me of my experience developing for Windows Phone. There were something like three or four distinct frameworks with nearly identical names. It’s already hard enough to google anything, and now you have your platform vendor running interference against you.
- grugagag 4y agoI share the sentiment. Microsoft has done an atrocious job at naming things here and created a lot of confusion.. Im not sure if this was their intended goal or it’s simply incompetence.
- becquerel 4y agoThe branding is really awful, yeah. .NET in its broadest sense is Microsoft's home-grown programming stack. This includes a runtime (the 'Common Language Runtime') which different languages target. The big one is C# but there's also F# and a few weird experiments which didn't pan out. It all started with '.NET Framework', which was the old Windows-only implementation. Eventually they rewrote everything to be cross-platform and called it '.NET Core'. Distinguishing between the two was confusing, so MS took the genius decision of rebranding it all as just '.NET'. So today you can make a project in .NET 5, .NET 6, .NET 7, etc., and they're all cross-plat. Various frameworks exist on top of .NET. The biggest one is ASP.NET Core, the web framework. And there's frameworks on top of that like Blazor. The runtime is JIT by default, but you do actually have AOT options nowadays, which is nice.
- tilt_error 4y agoBack in the day, I developed Windows applications using Visual C++ and Microsoft Foundation Classes (MFC). MFC had its set of quirks, and development was quite painful - I often wondered why a particular method was non-virtual, which would have helped me to adapt to certain requirements. I spent more time stepping through MFC code than in my own code, trying to understand what was going on. Eventually, I moved to other platforms and programming languages and had an epiphany that to function as a programmer of Windows software, I needed to know a lot of details about the platform. The literature was overflowing with articles about how you could utilize the platform more efficiently if only you knew some details under the hood. Moving away from Windows programming freed up a lot of mental resources. When I started programming in Java on top of all libraries and frameworks, I noticed that I was never really encouraged to look beyond the API. On the other hand, there were usually multiple implementations of those APIs, so if I wasn't satisfied with one, I could choose another. Of course, building GUIs in Java was (and still is) a real mess, so I didn't do that. Since then, I have programmed quite a bit in C# with the classic Forms-based platform on .NET, and this was a totally different experience from C++ and MFC. It was a delight. Reading this article was like traveling back in time to the olden days, where you are promised better performance if only you look under the hood. My honest suggestion is: don't do that, don't lock up resources on needing to understand how things are implemented under the hood or how the compiler handles these particular constructs. If these details cannot be hidden from you, and you need to build a large knowledge base around stuff that does not directly contribute to implementing your program, then choose another platform. I have managed to accumulate a growing understanding of how to solve problems, many times transferable between platforms and languages, but collecting all that insight into MFC minutiae was completely worthless.
- koonsolo 4y ago> MFC had its set of quirks, and development was quite painful I had the same experience. And then I learned win32, the C predecessor of MFC, and it all started to make sense. MFC is basically a horrible attempt to put an Object Oriented layer on top of win32. But they failed miserably at it. The API was so bad that I preferred just writing it in win32. And no, this was not a failure of OO, this was a failure of understanding and applying OO.
- sonicgear1 4y agoHacker news is just too keen on shitting on .NET In my experience switching from Java to .NET was an epiphany. The language is 10x more concise, async/await is the same as in javascript, the documentation and official guides are miles ahead of Java, and it's just a pleasure to work with.
- packetlost 4y ago> The language is 10x more concise C# is an amazing, well thought out language, agreed. > async/await is the same as in javascript How many variations of async/await coroutine programming are there? Not saying I disagree, but I think most of them are similar. > the documentation and official guides are miles ahead of Java This is where you lose me. It's been a few years, but the last time I looked at dotnet docs it was a steaming pile compared to most other languages I've looked at, including Java.
- hardware2win 4y agoThe moment you switch from C# to e.g C or CPP or other dev unfriendly tech is the moment you realize how c# lang design team and std lib API design teams outperformed
- bob1029 4y agoI spent a few minutes looking at a cmake script the other day and had some realizations about the industry. The simplicity of: dotnet build MySolution.sln Is the only reason I am still doing software for a living. I am completely over my tools. I just want to solve problems and build things now. If I have to lose 5% perf because I'm lazy, so be it. I'll hire someone to obsess over the 5% later on - and only if the customer complains.
- scarface74 4y agoI love .Net. It’s my favorite language and you can tell that it was written by competent adults that know how to design a language and the surrounding framework. I used .Net from 2007 with Windows CE/Compact Framework all the way to 2020. That being said, I would never tell am aspiring developer, especially one with a CS degree to learn it. Besides, Microsoft, it’s not used heavily in most companies that pay top of the market and it’s definitely not the language that I would chose to use if I had to go through a generic coding interview. Yes, I’m well aware that most of the 2.7 million developers in the US are “enterprise developers” and many use .Net. While .Net has been cross platform for years, it’s still the choice of mostly old school Windows shops.
- localghost3000 4y agoC# is an awesome language and I've worked professionally with it on and off over the course of my career. I'll never take another job at a shop that uses it though. Every job I've taken where it was the primary language forced windows on me and a whole bunch of "enterprisey" BS. They also tended to be spots where the engineering dept was considered a cost center and not a value creation unit. This is just my experience of course but I've got anecdata from colleagues which correlates. I even recall reading about the stigma attached to C#/.NET in "Cracking the Coding Interview" so there must be something there... I also found that startups (my preferred work env) tend to filter you out when you have too much of that on your resume which is super shitty IMO but it's a thing. I'd love to see that change. Until then I guess I'll stick with Kotlin and TypeScript.
- sebazzz 4y agoLinking a language to "enterprisey", especially a young language like C#, seems absurd to me. There are plenty of startups using Java, C#, etc. You don't have to be riding the Node.js bandwagon to be agile.
- mcluck 4y agoC# is over 20 years old now. In the land of language design, it's not a young language
- localghost3000 4y agoI agree! There are a LOT more startups that use Java than C# though despite C# being a better language in almost every respect. There’s a stigma attached to it right or wrong.
- klysm 4y agoI think some of that stigma is warranted because of the tooling and ecosystem around C#. The documentation contains frequent references to azure. Microsoft continues to place features into visual studio while making VS code actively bad. Many libraries are not free open source and you have to deal with weird licensing. Other ecosystems are definitely better.
- deleted 4y ago[deleted]