C# – Why is it necessary for every new api to be async

.net coreasyncasynchronous-programmingcprogramming-languages

I'm expressing my frustration here somewhat, but why do many new libraries only have asynchronous APIs? For example I'm creating a small utility to fetch a web page and parse some data from it. However, making use of HttpClient from the System.Net.Http namespace in .net core requires a lot of async and await boilerplate code, from this web page for example:

async static Task Main(string[] args)
{
}

Then the contents of Main:

HttpClient client = new HttpClient();
var response = await client.GetAsync("http://www.nzherald.co.nz/");
var pageContents = await response.Content.ReadAsStringAsync();
Console.WriteLine(pageContents);
Console.ReadLine();

I feel that c# has become a very wordy language and I'm not happy to have to code in the async style like this. I have been writing c# programs since .net 1.0, and I find it hard to reason about what is happening behind the scenes of the compiler: is creation of threads for async code going to impact performance? How can I throttle the rate of calls if I'm calling GetAsync in a loop?

My Question is: has the c# team gone in the right direction creating this async paradigm in the language? I feel that go's approach of creating a thread by just saying go myfunction() is less long-winded, but I know it has it's performance penalties due to the green-threads used.

Look at this question for example, it has the same type of concerns about async, and the single answer isn't very helpful.

Best Answer

I feel that c# has become a very wordy language and I'm not happy to have to code in the async style like this.

Oh, but that is not wordy at all. You are not writting something like this:

client.GetAsync("http://www.nzherald.co.nz/").Then
(
    response => response.Content.ReadAsStringAsync().Then
    (
        pageContents =>
        {
            Console.WriteLine(pageContents);
            Console.ReadLine();
        }
    )
);

And an API like the one above (probably a wrapper around ContinueWith) it won't handle exceptions properly, you would probably need an Error method with a callback to get the exceptions.

No, C# is not wordy.

Addendum: perhaps it is worth mentioning that if an async method returns a Task, it does not means you have to await it. If you want the Task to do something else with it (for example place it an array for Task.WaitAll or Task.WhenAll), you do not put the await keyword. Therefore, the await keyword is not redundant.


is creation of threads for async code going to impact performance?

No. Although some API do actually need to block on a Thread – and I think that is the case for listening on a socket, at least on Windows – it will use the ThreadPool, thus there won't be a lot of new Threads created.

That it will use the ThreadPool is not true for every API. Most will follow the following pattern:

  1. The library creates a TaskCompletionSource.

  2. The library sets a means to receive a notification. Callback, timer, message, whatever...

  3. The library sets code to react to the notification that will call SetResult, or SetException on the TaskCompletionSource as appropriate for the notification received.

  4. The library does the actual call to the external system.

  5. The library returns TaskCompletionSource.Task.

The call from step 4 goes to the external system. The external system responds, the response is received on the notification mechanism from step 2, then it sets the Task completed, and afterwards the continuation of the Task is scheduled (that continuation is what happens after your await).

How can I throttle the rate of calls if I'm calling GetAsync in a loop?

If you are awaiting it, then your code will not continue until it completes. Alternatively, if we are talking about having a limited number of Tasks concurrently (not being awaited), I would suggest to use a SemaphoreSlim. I would use it in conjunction with Task.WaitAny or Task.WhenAny.

Besides that, you probably will find await Task.Delay(milliseconds) useful. It will use the pattern I described above, around a timer, no Thread will be blocked waiting (unlike Thread.Sleep).


has the c# team gone in the right direction creating this async paradigm in the language?

I believe async/await is generally good. I agree that there is some overhead (creating Task objects, et. al.). However, C# is doing better than most languages.

I understand the blue-red argument...

... on that, I would argue that if you want to follow the idea of a pure core and an impure shell, you will find that pure and impure and async and "sync" are similar. The impure shell would deal with external systems. If you call an impure method from your method, your method is impure. Similarly, interacting with external systems is often async, and if you want to await an async method, you have to make your method async.

So, yeah, async propagating is annoying. The solutions is that the entry point will be impure (and async) it will deal with external systems (impure imperative shell) and call into your not async code (pure functional core), which returns to the shell for more interoperability. That way you do not have to make everything async, and you do not have to make your whole code impure. If you are isolating external systems (for ease of exchanging them and ease of testing), you are probably doing this already.

I feel that go's approach of creating a thread by just saying go myfunction() is less long-winded, but I know it has it's performance penalties due to the green-threads used.

Is your complain that await is longer to type than go? that explains "wordy".

Asynchronous does not mean multi-threaded. For instance, we can read for the hard disk without having a Thread waiting. You initialize the buffer where you will read, tell the operating system to tell the driver to tell the hard disk to write to that buffer (via DMA) and trigger an event. Then in the event we do TaskCompletionSource.SetResult and then system can schedule the continuation (your code after await). No Thread, and you didn't have to worry about it. And yes, that is the same pattern I described above.


I find it hard to reason about what is happening behind the scenes of the compiler

The compiler is rewriting your code as continuations. It is a state machine. This is not the first time the compiler rewrites your code as a state machine, that would have been co-routines iterators with yield return.

For an async method, each await means that the code afterwards will be a continuation the Task being awaited.

Please note that the Thread calling will not be waiting. Instead, the async method schedules the first Task, sets its continuation, and returns a Task that will completed when all the continuations created for the method completes.

Actually, it is a bit more complicated because there is error handling, and it works with try-catch-finally. The compiler does the rewriting.

That probably means that the calling Thread will eventually be free to do work, if that is the case it could be used to run scheduled Tasks. Sometimes the continuation will run on the same Thread that called it, sometimes it will run on a different one. It is up to the TaskScheduler. The default TaskScheduler will use the ThreadPool. And, yes, you can write a custom TaskScheduler.


Perhaps the interview Mads Torgersen: Inside C# Async can help you understand further. Strongly recommended.

Related Topic