Skip to content

Comment on Asynchronous clean-upparent

Comments

This post illustrates a common and unfortunate misconception about async/await.

It's not "hurr durr don't block thread" which is where a lot of developers stop reading at, at their own loss. And then come asking about green threads in github issues in a classic X Y problem fashion.

It is a paradigm where method calls represent an asynchronously produced result, a Task/Promise. Therefore, if all you do is always just await all the results right after calling async methods, you have not used the other 80% of the features.

Task<T> is about composing, chaining and interleaving the tasks, sometimes hundreds or thousands at a time, to achieve (sometimes massively) parallel and concurrent execution of application logic. And we are blessed with C# making it as easy as it gets.

You don't need the syntax tho. You can do green threading without function colouring. Rust might be the only language with an excuse to do it without green threading but only insofar as async doesn't presuppose native threading. But then why not just have a trait when you need to ensure you're the only thing executing on a single thread.

Please re-read the comment, thank you.

In addition, there are single-threaded and thread-per-core executors with different future bounds. It is that Tokio puts more requirements on Send and Sync because it is a proper implementation with worker-per-core (configurable to be otherwise) + work stealing.

async/await is a language design feature to enable fine-grained concurrency. Where "fine grained" means "more fine" than OS processes or threads allow.

Task<T> is about composing, chaining and interleaving the tasks, sometimes hundreds or thousands at a time, to achieve (sometimes massively) parallel and concurrent execution of application logic.

Replace Task<T> with Erlang processes and the statement holds. Except without coloured functions; without the async decision being in the wrong place; without codebases where dual versions of functions proliferate (DoSomething() / DoSomethingAsync()).

At its heart, concurrency is about being able to express multiple sequences of actions such that there's no undesired interaction between them when running. Thread blocking is one form of undesired interaction. So "hurr durr don't block thread" does matter even if it's not the only thing.

In C#, interleaving various tasks is as easy as

    var data1 = service1.GetData();
    var data2 = service2.GetAnotherData();

    var aggregate1 = Aggregate(await data1);

    var result = Handle(await aggregate1, await data2);
No need to deal with writing three lines per each operation to just schedule it in a "fork" way like in Java. In "colorless" (which is always a lie) async runtimes you have to go out of your way to make it concurrent. Perhaps Erlang process isn't coarse grained abstraction as you say, but there are multiple aspects that make Erlang problematic. And again, I will not tire of repeating it - the article about function coloring is actively harmful to the industry and is leading hundreds of developers with concurrency knowledge gaps assume that they need to avoid Task/Future-based code like plague when it is actually the best abstraction we have today for massively concurrent processes (if it was badly designed in whatever language of your choice - sorry).

In addition, because Task<T> is a thread-safe object in C#, you can apply all kinds of transformations and data chaining with LINQ on collections/sequences of those, or even together with parallel LINQ and tasks at the same time. Your simple average code will easily scale to all CPU cores if it does not have interdependencies/contention (a lot of LOB codebases don't, it's all straight line up to a DB or a third-party call(s)).

And last but not least, all BEAM-based languages are comparatively slow (hard performance ceiling is always imposed if you don't pay with static typing and full JIT/AOT) and unfortunately suffer from high heap footprint, even compared to the more throughput-focused GC modes in .NET and GC implementations in JVM. But no, developers are insistent on parroting quotes said 10 to 15 years ago instead of at least attempting to assess technologies on their merits of today.

"colorless" (which is always a lie)

No, colourless isn't a lie. Erlang doesn't have two types of function (sync and async). C#/Python now do. As a function/method implementer in those languages, for every single function implementation, I am faced with the following:

1. My function has to be async, because someone, somewhere, in the call chain of functions I want to invoke, decided to make their function async. So I have to deal with the downsides (lower performance, debugging complexity) whether I need the upsides or not.

2. I need to decide whether to make my function async because the decision hasn't been taken out my hands somewhere down the call stack. If I'm writing a library function that means I'm now having to judge how callers will use my function. If I decide async, then I've imposed constraints on the caller as per #1.

3. I need to implement sync & async versions of my function so as not to constrain the choices of my callers.

That emerges because of (1) the asymmetric constraint imposed by async/await and (2) the requirement for function implementers to make the decision, not callers. A sync function can't call an async one. That's the basis of colouring. It's not a lie.

the article about function coloring is actively harmful to the industry

No, the article about colouring very clearly explains the asymmetric nature of sync/async and the limitation it imposes. Its use of colour as a metaphor very clearly illustrates the issue. Sure, there's a risk that some people won't fully read and understand it - and then just parrot "yeah, coloured is bad". That's not the fault of the article though.

There's nothing inherently wrong with Futures as a concurrency construct: it's essentially enabling cooperative multitasking. The issue is that async/await as an implementation causes codebase bifurcation.

all BEAM-based languages are comparatively slow

Emphasis on "comparative". I don't disagree that in certain dimensions - notably raw compute performance and memory usage - BEAM languages are comfortably down the benchmark tables compared to C#. That doesn't always translate to real world practicality though (and even throughput is getting better given the active JIT work).

developers are insistent on parroting quotes said 10 to 15 years ago instead of at least attempting to assess technologies on their merits of today.

Futures have merit per above. Async/await brings syntactic convenience which, in isolation, is an improvement. But with it comes significant cost. Cost that isn't there with green threads and isn't intrinsic to Futures either. You can't just sweep those limitations under the carpet by promulgating the hubris that the arguments are old.

Do you have experience with C#? If yes, how much?

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.