Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Who is talking about .NET here?

.NET is out of the question when candidates flat-out refuse to work with GoF-style OO. No one claims to be excited about building modern distributed systems in Microsoft’s Java.

That said, the claim that goroutines are “poorly suited for fine-grained concurrency” is misleading. Define fine-grained.

Goroutines have near-zero startup cost and can scale to millions, whereas .NET’s async-await is still backed by a thread pool with scheduling overhead. The real issue isn’t execution but synchronization, and Go provides both message passing (chan) and low-level synchronization (sync.Mutex, atomic). If you’re writing workloads where every nanosecond counts, you’re probably using specialized primitives anyway—not .NET Tasks.

As for UX—want a return value? Use a channel. Need to sync? Use sync.WaitGroup. Need structured cancellation? context.Context. The alternative in .NET involves juggling Task.WhenAny(), ConfigureAwait(false), and state machine overhead—as if keeping all that junk in your head doesn’t come at a cost.

Goroutine orchestration isn’t “boilerplate”—it just gives you explicit control instead of forcing you into .NET’s awkward, OO-ridden async/await scaffolding.

But yeah, let’s rewrite Docker in .NET.



You both broke the site guidelines badly in this thread. If you'd please review https://news.ycombinator.com/newsguidelines.html and stick to the rules when posting here, we'd appreciate it.



> .NET is out of the question when candidates flat-out refuse to work with GoF-style OO.

If you think that it's all there is to .NET (besides your account, I have not encountered anyone seriously using the term in a long time), then I'm sorry you had to experience that specific codebase or a team environment that made you think this way. Pattern abuse and "GoF" is a Java-ism and comes from not having sufficiently expressive language together with particular culture of application architecture. However, please, stop repeating the same phrase in every message and actually look into what differentiates C#, Go, Java, etc. Could be a pleasant and illuminating learning experience.

And unlike Go, C# is a multi-paradigm language which is effective at functional and systems programming :)

> Define fine-grained.

Take a transport, like an HTTP client, send two/three/n requests concurrently. Or fire off two relatively quickly completing but still benefitting from multi-threading computations.

> Goroutines have near-zero startup cost and can scale to millions

This perception is the problem. Their cost is not near-zero. There is no free lunch: https://hez2010.github.io/async-runtimes-benchmarks-2024/tak...

> As for UX—want a return value? Use a channel. Need to sync? Use sync.WaitGroup. Need structured cancellation? context.Context. The alternative in .NET involves juggling Task.WhenAny(), ConfigureAwait(false), and state machine overhead—as if keeping all that junk in your head doesn’t come at a cost. Goroutine orchestration isn’t “boilerplate”—it just gives you explicit control instead of forcing you into .NET’s awkward, OO-ridden async/await scaffolding.

Do you actually have any experience with this or are you simply repeating something that you think is a problem? Context propagation issues are known and the resulting UX has a common complaint of being unwieldy in Go. CancellationToken or even implicit cancellation propagation in F# are far superior and long-time solved problems. I guess, in the land of Go, if Go does not provide, it must be impossible.

And why would anyone need to bother with explicit synchronization anway? That's what 'await' is for. Have more than one task? Just fire them off, and 'await' when you do need a result. No Task.WhenAny/All etc. necessary. These are for more complicated logic, or for mapping a sequence/collection of tasks onto results. Very handy and terse. Turns 25-line mess in Go into a sleek one-liner.


You both broke the site guidelines badly in this thread. If you'd please review https://news.ycombinator.com/newsguidelines.html and stick to the rules when posting here, we'd appreciate it.


The context problem is really annoying when using go. Many functions unnecessarily adding a context parameter so they can cancel a task. They made such nice syntax sugar for channels, why not also context?


Never used F#. Looked it up for thread cancellation and it is syntactic sugar similar to defer in Go. Kind of like an await return is a wrapper around f(x) callback.

Been years since I wrote Go code, until last week. Daily has become C#, python, and powershell. Took a day to fall back in and write a tool while learning a new GUI framework. Found the strengths and weaknesses of the GUI.

Personal, I find Go more pleasurable to work with than python and C#. Reality, the project requirements dictate the languages that maybe used. This is the reason why Go was chosen over the others listed and unlisted C, C++, Swift, rust, ...

Which would be a better experience with developing for iOS, Swift or C# or Go? If it was company's main solution or product, Swift, even when I will need to learn it.

>Take a transport, like an HTTP client, send two/three/n requests concurrently

That is business logic. Back end maybe embedded and this could become a DOS attack.

.NET implemented the code to manage task pools so you don't have to. N aysnc is really a set of Z handlers. With syntax sugar.

>Context propagation issues are known

Doesn't a context need to exist for two entities to communicate and no system can exist where context is not shared?

This is the adapter pattern in GoF, which in can be reduced to function g controlling the interactions between function a and b, where function a or b may act upon the agreed upon return type, callback type, or memory type. Address of the return, callback, and memory must be shared either with-in g() or a() and b() .. from CPU register up to system memory address.

>And why would anyone need to bother with explicit synchronization anway?

Dependent on business logic. Including which 3rd party libraries must be used for the solution. Software might be architecture for using Reactive objects which mimic async / await with more well defined and shared behavior. A custom managed thread maybe needed for more accurate time limits or an event loop is needed.

>sleek one-liner.

Be warned, one liners may seem useful, until they need to be debugged. There is a great difference between the two C# Reactive objects:

{

   doing.Where(x => x.CanDo()).Subscribe(x => x.ExtensionDigestion());

   doing.Where(x => {
#if DEBUG // Set ID_WITH_ISSUES_OR_TESTING to the proper ID when developing new user experience or interface. Use for debugging single issue bug that just happens with the same ID or same line of 3rd party product tie-ins.

           if (x.Id == ID_WITH_ISSUES_OR_TESTING) {
                   var i = DateTime.UtcNow();
           }
#endif

           return x.CanDo()
   }
   .Subscribe(x => {
           x.ExtensionDigestion();
   });
}


> Doesn't a context need to exist for two entities to communicate and no system can exist where context is not shared?

Please read the comment carefully. This refers to https://pkg.go.dev/context in Go.

> That is business logic. Back end maybe embedded and this could become a DOS attack.

It is not. There is nothing in lightweight concurrency that makes it "business logic"-specific.

> Be warned, one liners may seem useful, until they need to be debugged.

This has nothing to do with reactive patterns below. Task.WhenAll debugs just fine, and if one or multiple tasks throw, you get a nice AggregateException which can be further inspected with specific stack traces and such.

> Dependent on business logic. Including which 3rd party libraries must be used for the solution. Software might be architecture for using Reactive objects which mimic async / await with more well defined and shared behavior. A custom managed thread maybe needed for more accurate time limits or an event loop is needed.

Again. This is a reply to the comment about how in order to yield a value out of a goroutine, you need to manually fashion a place where to store it, and then to create a WaitGroup or a Cond/Mutex or maybe a channel and then manually wait on it. Something you get with a single 'await' keyword in C#. It has nothing to do with business logic or spawning an OS thread, or using a reactive framework.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: