4 ms·
The article seems specific to JavaScript, C# is different. > you cannot await in a sync function In C# it’s easy to block the current thread waiting for an as
by Const-me 2y ago
The article seems specific to JavaScript, C# is different.
> you cannot await in a sync function
In C# it’s easy to block the current thread waiting for an async task to complete, see Task.Wait method.
> since it will never resolve, you can also never await it
In C#, awaiting for things which never complete is not that bad, the standard library has Task.WhenAny() method for that.
> let's talk about C#. Here the origin story is once again entirely different
Originally, NT kernel was designed for SMP from the ground up, supports asynchronous operations on handles like files and sockets, and since NT 3.5 the kernel includes support for thread pool to dispatch IO completions: https://en.wikipedia.org/wiki/Input/output_completion_port https://en.wikipedia.org/wiki/Input/output_completion_port
Overlapped I/O and especially IOCP are hard to use directly. When Microsoft designed initial version of .NET, they implemented thread pool and IOCP inside the runtime, and exposed higher-level APIs to use them. Stuff like Stream.BeginRead / Stream.EndRead available since .NET 1.1 in 2003, the design pattern is called Asynchronous Programming Model (APM).
Async/await language feature introduced in .NET 4.5 in 2012 is a thin layer of sugar on top of these begin/end asynchronous APIs which were always there. BTW, if you have a pair of begin/end methods, converting into async/await takes 1 line of code, see TaskFactory.FromAsync.
- zamadatix 2y agoTask.Wait() is just using the normal "thread" (in the way the author defines it later) blocking logic to do that in said case but I think the author is trying to talk about pure async/await approaches there as an example of why you still want exactly that kind of non-async "thread" blocking to fall back on for differently colored functions. Task.WhenAny() is similar to Promise.any()/Promise.race(). I'm not sure this is where the author is focusing attention on though. Regardless if your execution is able to move on and out of that scope those other promises may still never finish or get cleaned up.
- the_mitsuhiko 2y agoYou're probably right that this is leaning in on JavaScript and Python more, but I did try to make a point that the origin story for this feature is quite a bit different between languages. C# is the originator of that feature, but the implications of that feature in C# are quite different than in for instance JavaScript or Python. But when people have a discussion about async/await it often loses these nuances very quickly. > Async/await language feature introduced in .NET 4.5 in 2012 is a thin layer of sugar on top of these begin/end asynchronous APIs which were always there. You are absolutely right. That said, it was a conscious decision to keep the callback model and provide "syntactic sugar" on top of it to make it work. That is not the only model that could have been chosen.
- cwills 2y agoSeems like this article conflates threads C# with asynchronous operations a little. The way I see it, threads are for parallel & concurrent execution of CPU-bound workloads, across multiple CPU cores. And typically use Task Parallel Library. Async/await won’t help here. Whereas async/await for IO bound workloads, and freeing up the current CPU thread until the IO operation finishes. As mentioned, syntactic sugar on top of older callback-based asynchronous APIs.
- the_mitsuhiko 2y agoI would make the argument it does not matter what the intention is, in practice people await CPU bound tasks all the time. In fact, here is what the offical docs[1] say: > You could also have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for writing async code. [1]: https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/async-scenarios https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous...
- coldtea 2y ago>You could also have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for writing async code. That's a scenario for a different reason though (to allow sharing the cpu between chunks of the calculation, e.g. to not freeze UI in JS). In that case you might want to async on CPU-bound code. But regarding maximizing utilization, you want async to take more advantage of a core's CPU when you got tasks waiting for IO, and threads to leverage more CPU cores when doing CPU bound tasks.
- zarzavat 2y agoThe difference in philosophy is: who is responsible for scheduling work? Is it the language, or is it the developer? In JS it's the language, for example node sits on top of libuv which is responsible for managing the thread pool and doing async IO. The advantages of this system are that it's very convenient, and the developer gets a safer single-threaded view over the multiple threads in use. The disadvantage is that the developer lacks control and if you actually want to write multithreaded APIs and not just use them then you have to drop down into a lower level language so you can talk to libuv or the OS. In C# there is no lower level language to drop down to.
- throwitaway1123 2y ago> In C#, awaiting for things which never complete is not that bad, the standard library has Task.WhenAny() method for that. It's not that bad in JS either. JS has both Promise.any and Promise.race that can trivially set a timeout to prevent a function from waiting infinitely for a non-resolving promise. And as someone pointed out in the Lobsters thread, runtimes that rely on multi-threading for concurrency are also often prone to deadlocks and infinite loops [1]. import { setTimeout } from 'node:timers/promises' const neverResolves = new Promise(() => {}) await Promise.any([neverResolves, setTimeout(0)]) await Promise.race([neverResolves, setTimeout(0)]) console.trace() [1] https://lobste.rs/s/hlz4kt/threads_beat_async_await#c_cf4wa1 https://lobste.rs/s/hlz4kt/threads_beat_async_await#c_cf4wa1
- cyberax 2y ago> Promise.race Ding! You now have a memory leak! Collect your $200 and advance two steps. Promise.race will waste memory until _all_ of its promises are resolved. So if a promise never gets resolved, it will stick around forever. It's braindead, but it's the spec: https://github.com/nodejs/node/issues/17469 https://github.com/nodejs/node/issues/17469
- throwitaway1123 2y agoThis doesn't even really appear to be a flaw in the Promise.race implementation [1], but rather a natural result of the fact that native promises don't have any notion of manual unsubscription. Every time you call the then method on a promise and pass in a callback, the JS engine appends the callback to the list of "reactions" [2]. This isn't too dissimilar to registering a ton of event listeners and never calling `removeEventListener`. Unfortunately, unlike events, promises don't have any manual unsubscription primitive (e.g. a hypothetical `removePromiseListener`), and instead rely on automatic unsubscription when the underlying promise resolves or rejects. You can of course polyfill this missing behavior if you're in the habit of consistently waiting on infinitely non-settling promises, but I would definitely like to see TC39 standardize this [3]. [1] https://issues.chromium.org/issues/42213031#comment5 https://issues.chromium.org/issues/42213031#comment5 [2] https://github.com/nodejs/node/issues/17469#issuecomment-349794909 https://github.com/nodejs/node/issues/17469#issuecomment-349... [3] https://github.com/cefn/watchable/tree/main/packages/unpromise#under-the-hood-step-3---unsubscribe https://github.com/cefn/watchable/tree/main/packages/unpromi...
- User23 2y ago> Originally, NT kernel was designed for SMP from the ground up, supports asynchronous operations on handles like files and sockets, and since NT 3.5 the kernel includes support for thread pool to dispatch IO completions: https://en.wikipedia.org/wiki/Input/output_completion_port https://en.wikipedia.org/wiki/Input/output_completion_port Say what you will about Microsoft in that era (and there's a lot to be said), the NT kernel team absolutely crushed it for their customers' use cases. IOCP were years ahead of anything else. I pretty much hated all of the userspace Win32 work I did (MIDL, COM, DCOM, UGGGGGGGGH), but the Kernel interfaces were wonderful to code against. To this day I have fond memories of Jeffrey Richter's book.
- wbl 2y agoIt's not enough to have a nicish abstraction, how did it work in practice and eek out performance? I've heard Bryan Cantrell say there wasn't much there and would be curious to really know what the truth is and more explanation on both sides.