Live data from Hacker News

Asynchronous Programming in C#

github.com

71–80 of 186 posts

Re: Asynchronous Programming in C#

#71
What's the right way to do throttled async in modern C#? For some context, we have a process that needs to make an API call for each row in a file - maybe hundreds or thousands. What's the best way beyond Wait()'ing for each one to get decent performance without DOS'ing the server?

Re: Asynchronous Programming in C#

#72
post #53

Earlier quoted context omitted.

Yeah, 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.

As 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?

In 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.

Re: Asynchronous Programming in C#

#73
post #6

We use async/await pretty much universally throughout our codebase today. One thing to keep in mind is that this mode of programming is actually not the most performant way to handle many problems. It is simply the most expedient way to manage I/O and spread trivial things across many cores in large, complex codebases. You can typically retrofit an existing code pile to be async-capable without a whole lot of sufferi…

> This mode of programming is actually not the most performant way to handle many problems. This is correct, it's for increasing _throughput_ in concurrent scenarios. Meaning that when your server is processing multiple requests at the same time, yielding back rather than busy-waiting allows a different request to progress instead (or even to start processing a queued request earlier). When waiting for I/O with anoth…

> This is correct, it's for increasing _throughput_ in concurrent scenarios.

I believe you mean the exact opposite. It decreases latency (because task B isn't blocked waiting for task A to complete) but it does so at the expense of decreased throughput. The context switches add overhead. If you just synchronously run A then B, the overall time would be shorter (higher throughput) because of less context switching overhead.

Re: Asynchronous Programming in C#

#74
post #6

We use async/await pretty much universally throughout our codebase today. One thing to keep in mind is that this mode of programming is actually not the most performant way to handle many problems. It is simply the most expedient way to manage I/O and spread trivial things across many cores in large, complex codebases. You can typically retrofit an existing code pile to be async-capable without a whole lot of sufferi…

> and spread trivial things across many cores in large, complex codebases

How are tasks spread across cores? My main experience with the "await" paradigm is from Python, which is primarily single threaded.

Re: Asynchronous Programming in C#

#75

What's the right way to do throttled async in modern C#? For some context, we have a process that needs to make an API call for each row in a file - maybe hundreds or thousands. What's the best way beyond Wait()'ing for each one to get decent performance without DOS'ing the server?

Can you use MaxDegreeOfParallelism or var throttler = new SemaphoreSlim(initialCount: MAX_CALLS)?

Re: Asynchronous Programming in C#

#76

What's the right way to do throttled async in modern C#? For some context, we have a process that needs to make an API call for each row in a file - maybe hundreds or thousands. What's the best way beyond Wait()'ing for each one to get decent performance without DOS'ing the server?

Fire off one task per row, but within each of those tasks use a SemaphoreSlim to rate limit your requests to the API.

Re: Asynchronous Programming in C#

#77

I would call myself an extremely experienced and knowledgable C# programmer with 10+ years of experience and even I found a few things surprising or new in this guide. I think C# async/await implementation is the biggest con on the .NET community, because it gets constantly hailed as one of the easiest ways of async programming but this guide itself proves to me that there are so many gotchas which are not obvious at…

Ex-.NET guy here, can confirm. Coming from JVM-land I was constantly told that async await is something that makes C#/.NET much better than Java. I personally could not understand why. Async await is not as easy as it looks, and most .NET programmers who I knew, would just hammer at things to make it work. "Hey, this is an HTTPClient call? Put an await in front of it?" "Oh, is the IDE showing an error? Try .Configure…

> most .NET programmers who I knew, would just hammer at things to make it work.

I hope the examples you listed are facetious or from the very early days of async/await in C#, otherwise I'd seriously question the skillset of the supposed .NET programmers.

Visual Studio is fairly good at handling incorrect use of async/await, and in all of the examples you listed, the actual solution should've been "read the IDE error, hit the bulb and apply the automatically suggested fix", not "ignore the IDE error and smash keyboard until it works".

ConfigureAwait usage is also not something you'll usually see outside of library code in modern C#.

Re: Asynchronous Programming in C#

#78
post #6

We use async/await pretty much universally throughout our codebase today. One thing to keep in mind is that this mode of programming is actually not the most performant way to handle many problems. It is simply the most expedient way to manage I/O and spread trivial things across many cores in large, complex codebases. You can typically retrofit an existing code pile to be async-capable without a whole lot of sufferi…

async/await is not for CPU-intensive parallelism. I think that's pretty much stated in the .NET docs. That's why Parallel Compute APIs like Parallel.ForEeach/For are not async. Their purpose is to enable non-blocking waits for IO, as well as to do stuff like animation on UI where you might want to execute procedural code over a larger timeframe.

The other reason those Parallel methods did use async/await is that async/await did not exist in .NET at the time those methods were introduced.

But good news! The upcoming .NET 6 release will have a Parallel.ForEachAsync method:

https://docs.microsoft.com/dotnet/api/system.threading.tasks...

Re: Asynchronous Programming in C#

#79
post #72

Earlier quoted context omitted.

As 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?

In 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.

Fair 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.

Re: Asynchronous Programming in C#

#80

What's the right way to do throttled async in modern C#? For some context, we have a process that needs to make an API call for each row in a file - maybe hundreds or thousands. What's the best way beyond Wait()'ing for each one to get decent performance without DOS'ing the server?

I personally used a semaphore for that. You create a semaphore with an initial count of MAX_REQS_PER_SECOND, create WORKER_COUNT of looping "worker" tasks that each call WaitAsync() on that semaphore before doing request (and don't call Release() after request is done), plus a separate task that does either

    Sleep(100);
    Release(MAX_REQS_PER_SECOND / 10);
or

   Sleep(1000 * WORKER_COUNT / MAX_REQS_PER_SECOND);
   Release(WORKER_COUNT);
in a loop, depending on what numbers make more sense.
Post reply on HN