…letting your code do absolutely nothing!
We’ve all been there, one way or the other. Either as users of an app or as the developer to whom users complained to: When a typical Win32 app is waiting for an operation to complete, we often get to see something like this:
In discussions about how to get a handle on such scenarios there are all kinds of suggestions, one of the most frequent ones being “You need to do this asynchronously: Just start a new thread! You can do that with Tasks.” And this is when people start introducing patterns like the following into their code which in most cases is not only unnecessary but is dangerously wrong!
So, let’s go back to the original problem for a second and figure out the root cause for that. Principally your app hangs like this when the so-called Message Loop of your app gets blocked. Every app (well, web apps excluded) has some kind of message loop, no matter if your app is a Windows Forms app, or if you based your app on WPF, UWP or even Xamarin on Android or iOS – for the latter, they are called Run Loops or Looper, but they are basically the same.
For a profound understanding of Async and Await it is helpful to know what happens behind the scenes, when and why a message loop gets blocked. After all, one of the primary domains of Async and Await is to address lacking responsiveness of apps. To achieve this, we need to keep the message loop clear as often as possible. So, let’s take a closer look at a rather unusual VB demo app, whose solution is called BlockingMessageLoop. You can find this demo in a GitHub Repo that you can clone or download from here. This demo displays a simple window with a button on the screen. You can click the button, which causes a loop to spin just to use up processor workload – which means it gets blocked, and you will end up with a result like that in the previous screenshot. So far so simple. This demo, however, is not your typical Windows Forms app. It implements everything without the WinForms base classes, so you can see – although very simplified – what actually happens behind the scenes, when you work with Forms, Controls and Events:
As you can see here, there are no classes for the parts of what usually would be the task of a Form. The demo only consists of a Module, starting with Sub Main, and that method first creates the main window by calling CreateWindow and CreateButton, which puts a Button in that Window. This is the way how Windows development already was done in pure C a couple of decades ago, and of course, you could or can – if you wanted – still do this the “original way” also in VB. What’s more important here is what Sub Main also does: It is starting the app’s Message Loop, which looks like this:
All Windows apps follow this same scheme. They create the windows, place control inside those windows (principally, controls are a special form of windows themselves), and then they start the message loop at some point, which runs as long as the main window is visible on the screen. Whenever something happens with the windows or the controls, Windows sends a message to the app informing it about the nature of the event: When the app needs to redraw the content of a window for example, this message is WM_PAINT; when the user clicks a Command Button, the message is WM_COMMAND – just to point out a few.
For the app to actually be getting those messages, it calls a Windows function named GetMessage. GetMessage waits until the next message for that app arrives, and while waiting it does not use up any processor workload – the app just idles. When the app then got the next message, it needs to run two additional functions: The first – TranslateMessage– is to process additional keyboard commands, and the second – DispatchMessage– is to send the message to a special message processing handler, usually called WndProc (Windows Procedure):
By the way: The DispatchMessage function of Windows knows this WndProc method, because this method has been passed as a delegate when the main window of the app had originally been created.
So, now assume, the user of your app clicks the button, and the app should do something which really needs a long time to complete. For example, calculating 100,000 digits of Pi. Or writing a big file on a memory stick. What happens if you put that code into WndProc? Well, WndProc does not return to DispatchMessage for a long time, either. Which means, the message loop cannot continue, which means no additional messages are coming through – your app becomes not responding. This is exactly what happens, if you were to put code for an excessive workload in a button’s click event handler in a Windows Forms or WPF app. And when that happens, it always causes Window to apply an emergency plan on your app: If your app does not react to any Windows messages within two seconds, Windows creates a new window which has the same title as that of the hanging app plus the famous words we all so love: “not responding”. Windows copies the last known content into this new window – only in a somewhat “blurry” version. Windows then hides the hanging app’s window from the screen and finally pretends, the new window is yours app’s main window for the time being. Now the user can at least move the hanging app’s window out of the way – or at least so it seems. Truth is, it is not at all the app’s window, but some fake copy.
Save us, DoEvents!
As Visual Basic developers of several years, in such scenarios we know what to do, do we not? Only the few really know, what it actually does internally, but we’ve all used it at some point: We just invoke DoEvents in our lengthy methods, and all of the sudden apps do no longer hang, as if by magic! Well, here’s the magic:
Some magic, right? All DoEvents does is to peek if there are any new messages. And if so, well, it simply does the same that the message loop does: It translates and dispatches the message, which causes the app no longer to hang, when we have a long-running worker method like in WndProc, because queued messages get processed by WndProc (and successively also by DefWindowsProc, which takes care of things like moving or resizing the window). But be careful: Since we allowed for messages to be processed within the still running worker method, the user can click on the button again, which causes WndProc to be called recursively another time. The chain: User clicks button, message loop calls DispatchMessage, DispatchMessage calls WndProc, WndProc runs worker code, worker code calls DoEvents, User has clicked the button again, DoEvents calls GetMessage and DispatchMessage, DispatchMessage calls WndProc again and so the worker code gets starteda second time – of course not in parallel, just recursively. So, you want to make sure to disable your controls, when the worker code starts, and only enable the controls again, when the worker code completes. The takeaway of this scenario: Keep the Message Loop clear if you want the most responsive app!
Wasting time by doing nothing
Now let’s look at the next demo in the GitHub repo, which is called SyncVsAsync. This demo is to show what happens if you have an app with a method which uses up a lot of processor workload, and you want to process something else in addition, like putting a big file on a storage device. Let’s say, on a memory stick:
When you start the app, it immediately begins with its heavy workload routine: It calculates 100,000 digits of Pi. Since it uses DoEvents while calculating, you’re still be able to control the app: All Windows messages get processed, so you can not only move the window around, you can also pick a folder on a memory stick, which you should have plugged in your computer beforehand. (This demo principally also works with the internal hard drive; unfortunately, those disks are so fast these days that everything happens too quickly to spot – since a thumb drive is considerable slower, a thumb drive does the trick, here).
If you’ve picked the folder, try this while the app is doing its calculations: Open the Task Manager by right-clicking the task bar. Sort the list by name. You should see the demo app in the list and, depending on how many cores your processor has, that it is using a pretty good amount of processor workload. Place the task manager, so you can see it and the app at the same time, click the button Write File Sync…, and observe what happens:
As soon as you click the button, the app starts to write the binary file of about 100 Mbyte on the memory stick. This causes two things: Incoming Windows messages are obviously no longer getting processed. The last update you see in the dialog is “Saving File…”, but after that everything stops. No display refresh. You can no longer move the window. But what you also notice: Your app does no longer use up ANY processor workload – it seems to do absolutely nothing, yet – check it out when it’s done – the file still gets written on the memory stick! So, what’s happening here? What is writing the actual file?
The truth is: Not the processor. Simplified put, by calling fs.Write in the code above, the processor tells some IO chip in your tablet or PC “Here, have the file at that memory address and with that length, and put it on the stick for me. Call me, when you’re done!”. Well, and then the processor sits there, suspends itself and waits to be reactivated, if you will. But what it also does not do: Running the message loop. Or calling at least DoEvents occasionally. The result: Your app hangs, until fs.Write internally received that call back, and the method can continue and eventually return.
Asynchronous Operations to the Best-Practice-Rescue!
Now let’s revisit the Task.Run code from the beginning. Do you notice now how senseless this code is? That sample code is explicitly spinning off another task, and the only thing this task does is to wait for an I/O-Operation to complete. Task.Run means: We are utilizing an additional thread, and with that we probably using up additional resources like another processor core. And by doing that, we’re confusing two concepts: I/O bound workload and CPU bound workload. The latter we apply, when we do some expensive work inside our code, like calculating 100.000 digits of pi. The first one, however, we need when we’re putting a file on a storage device. Or pulling data from the network. Both of which can be run asynchronously. Understanding the difference of those two is essential here!
Even without using Async and Await, .NET was always able to handle asynchronous scenarios. And that still works. But it gets, well, kind of messy when writing the code for that. Let’s find out what happens, when we restart the demo app, and this time click on the second button Write file Async… . Here’s the code for that:
Notice, that we use a slightly different version of the write method here: BeginWrite is initiating an asynchronous operation. We’re just telling the OS, it should offload the actual writing-to-device-operation to that component inside the computer, which is responsible for putting the file on the memory stick. Along, we’re passing the call-back method EndWriteFileProc.
Important: When you create a file stream, make sure you prepare that file stream for asynchronous operations. It is important to know that it does not suffice to just use the asynchronous versions of the file operation functions (BeginWrite, BeginRead, etc.). The OS needs to be prepared for asynchrony as well, if you will. You do this by setting the flags parameters you’re passing to the FileStream constructor manually, determine a buffer big enough to hold the full size of what you are going to write (or read), and pass a flag indicating that you want everything asynchronous underneath. If your code forgets this, the opposite of what you want to achieve will happen: Despite using asynchronous versions of the file operations, the actual IO work is done synchronously – wrapped in thread by the framework, it just feels to be asynchronous. But it is not.
When the operation completes, the OS should call that method, so we’d be notified and could do the cleanup:
Again, we can spot a problem, here: For each asynchronous operation, an app needs to kick off, it must put code in two different methods. Imagine, how a big code base would have looked like some 10 years ago, had an app already used asynchronous calls intensely. The result: pure spaghetti code!
A method with a spot which does absolutely nothing (thus letting the Message Loop loop)!
Now, there is (an even weirder) way to do this in just one method. When we adjust the kick off method to have the same signature as the callback, and we implement a simple state machine to differentiate if the method acts currently as the kick off or the callback part, we could unify both methods, thus having a much cleaner program flow:
And yes, I know. In the code above, I use the evil GoTo, and yes, I could have done that with If as well. But the separation of purposes would not have been so obvious, and that’s the most important point of this example!
In any case, we achieved the goal: The code is readably placed in one method, it is awaiting the callback, and while doing that, it does not claim the program control: Between the two parts, the message loop can run, and the app does not hang at any point while the file is written to its destination device.
And at last: Async and Await
Starting with Visual Studio 2012, Visual Basic and C# both introduced the Await operator to simplify such scenarios significantly. But coming up with new keywords for a programming language is not an easy task, because you’re always running the risk of breaking existing code. Imagine a method like this one:
What’s going to happen with that if Await as a new keyword became available per se? The code would break. This is where Async comes into play. It does not do much, when applied to a method. It is just decorating a method and telling the compiler: “You will very likely find one or more Await operators in this method, so, please build the required state machines already for awaiting the asynchronous calls which return tasks.” Which results in important takeaway #1: No Async keyword on the method’s signature, no Await allowed in the method’s body. It’s as simple as that. Just decorating a method with Async does not do anything to the method. (Well, only internally, but it does not change the nature of the method at all.)
Important takeaway #2: The Await operator awaits Tasks. (Well, to be completely accurate, Await awaits everything that exposes the so called awaitable pattern, and Tasks are one of them you will be dealing with quite often). Tasks are promises that a certain method will be executed at some point. Does that mean a new task always runs parallel to the current task (where all the UI related stuff happens)? Not at all! It might at some point. But it absolutely does not have to run in parallel. Asynchronous just means “will be happening” or “a result will be available at some point”.
There is a rule that a certain method in the .NET Framework (or in any other library, although there is no guarantee for this) is returning such a promise through Task, namely if the method’s name ends with “Async”. Thus, it’s comparatively easy to figure out the methods for that: In our Write-To-Memory-Stick sample we are using the Write and the Flush methods. If we resort to IntelliSense for our FileStream variable fs, we quickly discover the methods WriteAsync and FlushAsync– both doing the same as their synchronous counterparts, but returning Task, thus being able to be awaited.
Oh, and one more and very important thing: If you do not have a really good and exceptional reason, and you absolutely know what you are doing, you should never call Async methods which do not return Task or Task(Of SomeType). Or, for us VBs in other words: only call Async Functions yourself, avoid calling Async Subs directly. You may ask yourself now, when you are not supposed to call Async Subs, why would you implement them to begin with, when they get never called? Well, there are some type of Subs which you never call, but which get called in your apps: Event Handlers! Those can be under normal circumstances considered to be save if they are Async Subs, because your code does not usually call them directly, but rather they get called. The reason for that is simple: Tasks are promises, so they include a callback mechanism when the actual task is done. This is pretty much what we learned when we used BeginWrite and EndWrite in a previous sample. The callback method EndFileProc was the promise, if you will, in that sample, just done the old-fashioned way. A Task now can encapsulate both the initiating call and the callback in one entity. And awaiting a task enqueues the processing of that promise and picks up the control flow when the promise is delivered. In the meantime: the message loop runs while the actual method just sits there and does nothing. (Again, almost in same way as we did it in the sample of the state machine – take another look!) What’s important to know: A method returning a Task (a Function of type Task) can deliver that promise back to the caller. A Sub never returns anything to the caller. The methods inside an Async Sub gets executed all right, but there is no Task (promise) which can be returned – it is a one-way street, and that’s the reason why it is also called “fire and forget” (and why it also should always have an exception handler, because crashing inside an Async Sub can crash the whole app. In some scenarios, even without any exception message dialog or crash report – your app would just be gone.) Let’s put this together now:
As we just pointed out: Async Subs are OK, when they have Event Handler characteristics. Our Click event handler can therefore be an Async Sub. What is curious though on first glance is the next method WriteFileCommandProcAsync: Although it is a Function, it does not return Task, and the compiler on top does not complain that the method is not returning any result. But that’s OK! Since we decorated this method with Async and we’re returning Task, the method’s body holds the code for the promise. So, inside an Async Function we are returning the result of that promise not that promise itself. Which means:
That means on the caller side:
And now it’s up to you: Go, experiment! Refactor your apps to be more responsive, and discover the goodness of Async and Await!
Happy VB coding, leave your comments, stay tuned for the next part, and Tschüss from Germany!
Klaus Löffelmann
(Follow me on twitter @loeffelmann)