5 ms·
C# was my first exposure to async/await back in 2015 and I initially had trouble wrapping my head around various details (i.e. ConfigureAwait etc.). I think the
by maxpert 5y ago
C# was my first exposure to async/await back in 2015 and I initially had trouble wrapping my head around various details (i.e. ConfigureAwait etc.). I think the languages that have done best job in removing all that detail are Go and Elixir (Beam based languages). Which if you pay attention removed the overhead of rewiring your brain to do async/await all the way down. I repeated async/await systems recently with Kotlin coroutines in JVM world and again same problem, this time due to my prior knowledge I did hit the ground running but avg Joe had to relearn.
- jayd16 5y agoI think it's clear from the languages given that the added complexity is to handle UI workflows, no?
- Hercuros 5y agoYou can have UI workflows just as well with a language that has (runtime-managed) green threads, like Go and Elixir. It's just that you sometimes have to make sure that certain green threads have their actions scheduled on the UI (OS) thread for backwards compatibility reasons. For example, the Scala ZIO library provides the means to control where (on which OS thread) code is scheduled in a very intuitive way that does not involve ConfigureAwait hacks. ZIO is not green threads and rather more like async/await, but a similar approach could be implemented in languages with runtime-managed green threads as well.
- jayd16 5y ago>you sometimes have to make sure that certain green threads have their actions scheduled on the UI (OS) thread But that management is the crux of the issue. The complexity of hopping into and out of contexts is what is exposed with async/await and coroutine scopes and hidden by the more simple syntax.
- gambler 5y agoYeah, I don't like Go in most respects, but their approach to concurrency is way more intuitive than async/await. That said, Go doesn't have any standard promise or futures libraries, which is quite ridiculous. Yes, you can roll your own or go get one, but that is something basic should be in the language.
- Jtsummers 5y agoAs someone who has never really used promises or futures, what do they add, or how do they make concurrent programming easier or clearer than what's currently in Go?
- gambler 5y agoIn short, they provide clean API for checking on completion and error states of a long-running process from another thread without blocking that thread. This is an essential pattern for UI-related tasks, including web pages.
- Jtsummers 5y agoFair enough, I think we are thinking of futures slightly differently. I was thinking primarily about the deferred action to get a result (in which case channels and goroutines are equivalent with a select to handle, perhaps, an error result). You're also thinking of the other capabilities around task management and monitoring which I was not.
- phamilton 5y agoAn easy example of where having promises is nice: a := getFoo() b := getBar() c := getBaz() If you want to do all 3 in parallel, you may need to use channels and wait groups and stuff. In a language with promises: const a = getFoo(); const b = getBar(); const c = getBaz(); await a await b await c or even better const [a,b,c] = await Promise.all([getFoo(), getBar(), getBaz()]) If/when go generics become available, I expect to see some libraries that make things easier in golang.
- vips7L 5y agoIt's kind of odd that JetBrains chose async/await for Kotlin considering the JVM is going towards the Go approach for virtual threads. I guess they had to since they wanted to support android/js?
- pjmlp 5y agoThe decision happened before Loom came to be. It is yet another example of impedance mismatch from guest languages, when the platform moves into another direction. The platform language gets the true way, while the guest languages get the hard decision how to combine multiple approaches, and libraries that only use the new platform APIs.
- SureshG 5y agoYeah that decision make sense for multi platform. Suspending functions will work on JS and Kotlin Native.
- jayd16 5y agoIt makes sense for UI patterns and any pattern where you need to bind to a specific OS thread.
- kevingadd 5y agoIMO ConfigureAwait is just the result of a failure to fully consider the implications of various design choices early on. To be fair, it is a tough problem, but the ergonomics ended up being horrible and the default they chose was probably the wrong default. There are some other choices they made that are arguably not the right ones - for example, async code can do some of its initial execution on the calling thread and do the rest wherever continuations get scheduled (which is configurable...) which means you have to have exception handling in two places and the way the exception handling works will be different (the article calls this out). It is possible to avoid this by having the initial call to the async function only create the task but not run any of it - of course, there are reasons not to do it, performance being one of them, so it makes sense that they did it... it's just bad to optimize by default instead of making code simpler and more reliable. My least favorite decision is that inexplicably, async/await state machines are very error prone... if any part of your codebase accidentally invokes a continuation twice, the state machine will potentially begin running twice or even start running again from the beginning with the same local variables. Fixing this would have been as simple as setting a bool at the end and checking it at the beginning, but for some reason they are dead-set on not fixing it. Premature optimization once again. The existence of 'async void' is also just a complete trainwreck. They shouldn't have allowed it, especially since an 'async Task' that discards its result is just as easy. The approach to cancellation (intrusive only) is also needlessly complex and gross. Putting a Dispose method on a Task would allow consumers of any async API to cleanly signal that they no longer need the result of a Task and any implementation would be able to observe this without anyone having to introduce a new method overload that takes a CancellationToken, not to mention that the intrusive cancellation design creates extra garbage on the heap. Really not obvious to me why they did this instead of reusing 'using x' and IDisposable.