30

I'm trying to wrap my head around all of the Async stuff that's been added into the .NET framework with the more recent versions. I understand some of it, but to be honest, personally I don't think it makes writing asynchronous code easier. I find it rather confusing most of the time and actually harder to read than the more conventional approaches that we used before the advent of async/await.

Anyway, my question is a simple one. I see a lot of code like this:

var stream = await file.readAsStreamAsync()

What's going on here? Isn't this equivalent to just calling the blocking variant of the method, i.e.

var stream = file.readAsStream()

If so, what's the point in using it here like this? It doesn't make the code any easier to read so please tell me what I am missing.

user3700562
  • 693
  • 11
  • 23
  • 1
    It doesn't make the async code any easier to read than the sync code, but think about how you'd write the same async code without the `await` keyword... That's tougher to read (and write). Your examples are *functionally* equivalent, but the first one won't block a thread just to wait for I/O. – Lucas Trzesniewski Dec 15 '15 at 09:34
  • 1
    Wait a minute, what are you talking about when you say "more conventional approaches"? It kind of sounds like you never used asynchronous I/O, but rather posted your work on a separate thread and done synchronous I/O there. Am I right? – Luaan Dec 15 '15 at 09:42
  • Related: [Why Use Async/Await Over Normal Threading or Tasks?](http://stackoverflow.com/questions/18298946/why-use-async-await-over-normal-threading-or-tasks). – GolezTrol Dec 15 '15 at 10:55
  • @user3700562: I recommend you start with my [`async` intro](http://blog.stephencleary.com/2012/02/async-and-await.html) and follow up with my [MSDN article on async best practices](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx) and the [official TAP documentation](https://msdn.microsoft.com/en-us/library/hh873175(v=vs.110).aspx). If you want to go really deep, [Jon Skeet's eduasync](http://codeblog.jonskeet.uk/category/eduasync/) will show you more than you ever need to know. – Stephen Cleary Dec 15 '15 at 13:29

5 Answers5

16

The result of both calls is the same.

The difference is that var stream = file.readAsStream() will block the calling thread until the operation completes.

If the call was made in a GUI app from the UI thread, the application will freeze until the IO completes.

If the call was made in a server application, the blocked thread will not be able to handle other incoming requests. The thread pool will have to create a new thread to 'replace' the blocked one, which is expensive. Scalability will suffer.

On the other hand, var stream = await file.readAsStreamAsync() will not block any thread. The UI thread in a GUI application can keep the application responding, a worker thread in a server application can handle other requests.

When the async operation completes, the OS will notify the thread pool and the rest of the method will be executed.

To make all this 'magic' possible, a method with async/await will be compiled into a state machine. Async/await allows to make complicated asynchronous code look as simple as synchronous one.

Jakub Lortz
  • 14,616
  • 3
  • 25
  • 39
  • 1
    That makes sense, thank you! The code in question is in an ASP.NET MVC controller, though so I wonder what good it is there? At the end of the day the HTTP response to the client can only be sent once the file (or whatever it is you are doing) has been fully processed. What's the thread supposed to do in the meantime? Won't it just sit the and wait for the operation to complete or is it freed to serve other incoming requests while the operation is still in progress? – user3700562 Dec 16 '15 at 10:31
  • 1
    @user3700562 The thread can handle other requests. You will not see a performance difference with a single request, but when there are hundreds concurrent requests, blocking I/O will waste a lot of resources and hurt performance. – Jakub Lortz Dec 16 '15 at 10:40
7

It makes writing asynchronous code enormously easier. As you noted in your own question, it looks as if you were writing the synchronous variant - but it's actually asynchronous.

To understand this, you need to really know what asynchronous and synchronous means. The meaning is really simple - synchronous means in a sequence, one after another. Asynchronous means out of sequence. But that's not the whole picture here - the two words are pretty much useless on their own, most of their meaning comes from context. You need to ask: synchronous with respect to what, exactly?

Let's say you have a Winforms application that needs to read a file. In the button click, you do a File.ReadAllText, and put the results in some textbox - all fine and dandy. The I/O operation is synchronous with respect to your UI - the UI can do nothing while you wait for the I/O operation to complete. Now, the customers start complaining that the UI seems hung for seconds at a time when it reads the file - and Windows flags the application as "Not responding". So you decide to delegate the file reading to a background worker - for example, using BackgroundWorker, or Thread. Now your I/O operation is asynchronous with respect to your UI and everyone is happy - all you had to do is extract your work and run it in its own thread, yay.

Now, this is actually perfectly fine - as long as you're only really doing one such asynchronous operation at a time. However, it does mean you have to explicitly define where the UI thread boundaries are - you need to handle the proper synchronization. Sure, this is pretty simple in Winforms, since you can just use Invoke to marshal UI work back to the UI thread - but what if you need to interact with the UI repeatedly, while doing your background work? Sure, if you just want to publish results continuously, you're fine with the BackgroundWorkers ReportProgress - but what if you also want to handle user input?

The beauty of await is that you can easily manage when you're on a background thread, and when you're on a synchronization context (such as the windows forms UI thread):

string line;
while ((line = await streamReader.ReadLineAsync()) != null)
{
  if (line.StartsWith("ERROR:")) tbxLog.AppendLine(line);
  if (line.StartsWith("CRITICAL:"))
  {
    if (MessageBox.Show(line + "\r\n" + "Do you want to continue?", 
                        "Critical error", MessageBoxButtons.YesNo) == DialogResult.No)
    {
      return;
    }
  }

  await httpClient.PostAsync(...);
}

This is wonderful - you're basically writing synchronous code as usual, but it's still asynchronous with respect to the UI thread. And the error handling is again exactly the same as with any synchronous code - using, try-finally and friends all work great.

Okay, so you don't need to sprinkle BeginInvoke here and there, what's the big deal? The real big deal is that, without any effort on your part, you actually started using the real asynchronous APIs for all those I/O operations. The thing is, there aren't really any synchronous I/O operations as far as the OS is concerned - when you do that "synchronous" File.ReadAllText, the OS simply posts an asynchronous I/O request, and then blocks your thread until the response comes back. As should be evident, the thread is wasted doing nothing in the meantime - it still uses system resources, it adds a tiny amount of work for the scheduler etc.

Again, in a typical client application, this isn't a big deal. The user doesn't care whether you have one thread or two - the difference isn't really that big. Servers are a different beast entirely, though; where a typical client only has one or two I/O operations at the same time, you want your server to handle thousands! On a typical 32-bit system, you could only fit about 2000 threads with default stacksize in your process - not because of the physical memory requirements, but just by exhausting the virtual address space. 64-bit processes are not as limited, but there's still the thing that starting up new threads and destroying them is rather pricy, and you are now adding considerable work to the OS thread scheduler - just to keep those threads waiting.

But the await-based code doesn't have this problem. It only takes up a thread when it's doing CPU work - waiting on an I/O operation to complete is not CPU work. So you issue that asynchronous I/O request, and your thread goes back to the thread pool. When the response comes, another thread is taken from the thread pool. Suddenly, instead of using thousands of threads, your server is only using a couple (usually about two per CPU core). The memory requirements are lower, the multi-threading overheads are significantly lowered, and your total throughput increases quite a bit.

So - in a client application, await is only really a thing of convenience. In any larger server application, it's a necessity - because suddenly your "start a new thread" approach simply doesn't scale. And the alternative to using await are all those old-school asynchronous APIs, which handle nothing like synchronous code, and where handling errors is very tedious and tricky.

Luaan
  • 62,244
  • 7
  • 97
  • 116
3
var stream = await file.readAsStreamAsync();
DoStuff(stream);

is conceptually more like

file.readAsStreamAsync(stream => {
    DoStuff(stream);
});

where the lambda is automatically called when the stream has been fully read. You can see this is quite different from the blocking code.

If you're building a UI application for example, and implementing a button handler:

private async void HandleClick(object sender, EventArgs e)
{
    ShowProgressIndicator();

    var response = await GetStuffFromTheWebAsync();
    DoStuff(response);

    HideProgressIndicator();
} 

This is drastically different from the similar synchronous code:

private void HandleClick(object sender, EventArgs e)
{
    ShowProgressIndicator();

    var response = GetStuffFromTheWeb();
    DoStuff(response);

    HideProgressIndicator();
} 

Because in the second code the UI will lock up and you'll never see the progress indicator (or at best it'll flash briefly) since the UI thread will be blocked until the entire click handler is completed. In the first code the progress indicator shows and then the UI thread gets to run again while the web call happens in the background, and then when the web call completes the DoStuff(response); HideProgressIndicator(); code gets scheduled on the UI thread and it nicely finishes its work and hides the progress indicator.

Joren
  • 14,472
  • 3
  • 50
  • 54
2

What's going on here? Isn't this equivalent to just calling the blocking variant of the method, i.e.

No, it is not a blocking call. This is a syntactic sugar that the compiler uses to create a state machine, which on the runtime will be used to execute your code asynchronously.

It makes your code more readable and almost similar to code that runs synchronously.

GolezTrol
  • 114,394
  • 18
  • 182
  • 210
Christos
  • 53,228
  • 8
  • 76
  • 108
  • 1
    I would appreciate if the downvoters could explain the wrong. Thanks – Christos Dec 15 '15 at 09:36
  • 2
    `await` suggests, and OP assumes, that it waits there for the asynchronous call to complete. That waiting would take place in the calling thread making it blocking. I think your answer may be true, but it doesn't explain well what is actually going on. If I would have asked OPs question (and I could have), I wouldn't have benefited much from this answer and still be oblivous as to why you would immediately await the task that was returned. – GolezTrol Dec 15 '15 at 11:07
1

It looks like you're missing what is all this async / await concept is about.

Keyword async let compiler knows that method may need to perform some asynchronous operations and therefore it shouldn't be executed in normal way as any other method, instead it should be treated as state machine. This indicates that compiler will first execute only part of method (let's call it Part 1), and then start some asynchronous operation on other thread releasing the calling thread. Compiler also will schedule Part 2 to execute on first available thread from the ThreadPool. If asynchronous operation is not marked with keyword await then its not been awaited and calling thread continues to run till method is finished. In most cases this is not desirable. That's when we need to use keyword await.

So typical scenario is :

Thread 1 enters async method and executes code Part1 ->

Thread 1 starts async operation ->

Thread 1 is released, operation is underway Part2 is scheduled in TP ->

Some Thread (most likely same Thread 1 is its free) continues to run method till its end (Part2) ->

Fabjan
  • 13,506
  • 4
  • 25
  • 52