Asynchronous Programming - Async from the Start

Recent versions of the Microsoft .NET Framework make it easier than ever to write responsive, high-performance applications­ via the async and await keywords—it’s no exaggeration to say that they’ve changed the way we .NET developers write software. Asynchronous code that used to require an impenetrable web of nested callbacks can now be written (and understood!) almost as easily as sequential, synchronous code.

There’s ample material already on creating and consuming async methods, so I’ll assume you’re familiar with the basics. If you aren’t, the Visual Studio Documentation page at msdn.com/async can get you up to speed.

Most of the documentation about async warns that you can’t just plug an async method into existing code; the caller itself needs to be async. In the words of Lucian Wischik, a developer on the Microsoft language team, “Async is like the zombie virus.” So how do you build async into the very fabric of your application, right from the beginning, without resorting to async void? I’m going to show you over the course of several refactorings of the default UI startup code, both for Windows Forms and Windows Presentation Foundation (WPF), transforming the UI boilerplate into an object-oriented design and adding support for async/await. Along the way, I’ll also explain when it does and doesn’t make sense to use “async void.”

In this article, the main walk-through focuses on Windows Forms; WPF requires additional changes, which can get distracting. In each step, I’ll first explain the changes with a Windows Forms application, and then discuss any differences needed for the WPF version. I show all the basic code changes in the article, but you can see completed examples (and the intermediate revisions) for both environments in the accompanying online code download.

First Steps

The Visual Studio templates for Windows Forms and WPF applications don’t really lend themselves to using async during startup (or to customizing the startup process in general). Although C# strives to be an object-oriented language—all code has to be in classes—the default startup code nudges developers toward placing logic in static methods alongside Main, or in an overly complicated constructor for the main form. (No, it’s not a good idea to access the database inside the MainForm constructor. And, yes, I’ve seen it done.) This situation has always been problematic, but now with async, it also means there’s no clear opportunity to have the application initialize itself asynchronously.

To start, I created a new project with the Windows Forms Application template in Visual Studio. Figure 1 shows its default startup code in Program.cs.

It’s not as easy with WPF. The default WPF startup is quite opaque, and even finding any code to customize is difficult. You can put some initialization code in Application.OnStartup, but how would you delay showing the UI until you’ve loaded the necessary data? The first thing I need to do with WPF is expose the startup process as code I can edit. I’ll get WPF to the same starting point as Windows Forms, and then each step of the article is similar for both.

After creating a new WPF application in Visual Studio, I create a new class, called Program, with the code in Figure 2. To replace the default startup sequence, open the project properties and change the startup object from “App” to the newly created “Program.”

If you use “Go to Definition” on the call to InitializeComponent in Figure 2, you’ll see the compiler generates the equivalent Main code as when you use App as the startup object (which is how I can open the “black box” for you here).

Toward an Object-Oriented Startup

First, I’ll do a small refactoring of the default startup code to push it in an object-oriented direction: I’ll take the logic out of Main and move it to a class. To do that, I’ll make Program a non-static class (as I said, the defaults push you in the wrong direction) and give it a constructor. Then I’ll move the setup code to the constructor, and add a Start method that will run my form.

I’ve called the new version Program1, and you can see it in Figure 3. This skeleton shows the core of the idea: to run the program, Main now creates an object and calls methods on it, just as with any typical object-oriented scenario.

Decoupling the Application from the Form

Nevertheless, that call to Application.Run that takes a form instance (at the end, in my Start method) poses a few problems. One is a generic architectural concern: I don’t like that it ties my application’s lifetime to displaying that form. This would be OK for many applications, but there are applications that run in the background that should not display any UI when they start, except maybe an icon in the taskbar or the notification area. I know I’ve seen some that briefly flash a screen when they launch, before disappearing. My bet is that their startup code follows a similar process, and then they hide themselves as soon as possible when the form is finished loading. Admittedly, that particular problem isn’t necessary to solve here, but the separation will be of critical importance for initializing asynchronously.

Instead of Application.Run(m_mainForm), I’ll use the overload of Run that doesn’t take an argument: It starts the UI infrastructure without tying it to any particular form. This decoupling means I have to show the form myself; it also means that closing the form will no longer quit the app, so I need to wire that up explicitly, too, as shown in Figure 4. I’ll also use this opportunity to add my first hook for initialization. “Initialize” is a method I’m creating on my form class to hold any logic I need for initializing it, such as retrieving data from a database or a Web site.

Figure 4 Program2, the Message Loop Is Now Separate from the Main Form

In the WPF version, the app’s StartupUri determines what window to show when Run is called; you’ll see it defined in the App.xaml markup file. Unsurprisingly, the Application default ShutdownMode setting of OnLastWindowClose shuts down the application when all the WPF windows have closed, so that’s how the lifetimes get tied together. (Note that this differs from Windows Forms. In Windows Forms, if your main window opens a child window and you close just the first window, the application will exit. In WPF, it won’t exit unless you close both windows.)

To accomplish the same separation in WPF, I first remove the StartupUri from App.xaml. Instead, I create the window myself, initialize it and show it before the call to App.Run:

To accomplish that explicit shutdown, I’ll attach an event handler for MainWindow.Closed.

Of course, WPF does a better job of separating concerns, so it makes more sense to initialize a view model rather than the window itself: I’ll create a MainViewModel class and create my Initialize method there. Similarly, the request to close the app should also go through the view model, so I’ll add a “CloseRequested” event and a corresponding “RequestClose” method to the view model. The resulting WPF version of Program2 is listed in Figure 5 (Main is unchanged, so I won’t show it here).

Pulling out the Hosting Environment

Now that I’ve separated Application.Run from my form, I want to handle another architectural consideration. Right now, Application is deeply embedded in the Program class. I want to “abstract out” this hosting environment, so to speak. I’m going to remove all the various Windows Forms methods on Application from my Program class, leaving only the functionality related to the program itself, as shown with Program3 in Figure 6. One last piece is to add an event on the program class so the link between closing the form and shutting down the application is less direct. Notice how Program3 as a class has no interaction with Application!

Separating the hosting environment has a few benefits. For one, it makes testing easier (you can now test Program3, to a limited extent). It also makes it easier to reuse the code elsewhere, perhaps embedded into a larger application or a “launcher” screen.

The decoupled Main is shown in Figure 7—I’ve moved the Application logic back to it. This design makes it easier to integrate WPF and Windows Forms, or perhaps to gradually replace Windows Forms with WPF. That’s outside the scope of this article, but you can find an example of a mixed application in the accompanying online code. As with the prior refactoring, these are nice things but not necessarily crucial: The relevance to the “Task at hand,” so to speak, is that it’s going to make the asynchronous version flow more naturally, as you’ll soon see.

Long-Awaited Asynchrony

Now, finally, the payoff. I can make the Start method asynchronous, which lets me use await and make my initialization logic asynchronous. As per convention, I’ve renamed Start to StartAsync, and Initialize to InitializeAsync. I’ve also changed their return type to async Task:

In order to explain how this works—and solve a subtle but important problem—I need to explore in detail what’s going on with async/await.

The true meaning of await: Consider the StartAsync method I presented. It’s important to realize that (typically), when an async method reaches the await keyword, it returns. The executing thread continues, just as it would when any method returns. In this case, the StartAsync method reaches “await m_mainForm.InitializeAsync” and returns to Main, which continues, calling Application.Run. This leads to the somewhat counterintuitive result that Application.Run is likely to execute before m_mainForm.Show, even though sequentially it occurs after m_mainForm.Show. Async and await do make asynchronous programming easier, but it’s still by no means easy.

That’s why async methods return Tasks; it’s that Task completing that represents the async method “returning” in the intuitive sense, namely when all of its code has run. In the case of StartAsync, it means that it has completed both InitializeAsync and m_mainForm.Show. And this is the first problem with using async void: Without a task object, there’s no way for the caller of an async void method to know when it has finished executing.

How and when does the rest of the code run, if the thread has moved on and StartAsync has already returned to its caller? This is where Application.Run comes in. Application.Run is an infinite loop, waiting for work to do—mainly processing UI events. For example, when you move your mouse over the window, or click a button, the Application.Run message loop will dequeue the event and dispatch the appropriate code in response, and then wait for the next event to come in. It’s not strictly limited to the UI, though: Consider Control.Invoke, which runs a function on the UI thread. Application.Run is processing these requests, too.

In this case, once InitializeAsync completes, the remainder of the StartAsync method will be posted to that message loop. When you use await, Application.Run will execute the rest of the method on the UI thread, just as if you’d written a callback using Control.Invoke. (Whether the continuation should occur on the UI thread is controlled by ConfigureAwait. You can read more about that in Stephen Cleary’s March 2013 article on best practices in asynchronous programming at msdn.com/magazine/jj991977).

This is why it was so important to separate Application.Run from m_mainForm. Application.Run is running the show: it needs to be running in order to process the code after the “await,” even before you’re actually showing any UI. For example, if you try moving Application.Run out of Main and back into StartAsync, the program will just quit immediately: Once execution hits “await InitializeAsync,” control returns to Main, and then there’s no more code to run, so that’s the end of Main.

This also explains why the use of async has to start from the bottom up. A common but short-lived antipattern is to call Task.Wait instead of await, because the caller isn’t an async method, but most likely it will deadlock immediately. The problem is that the UI thread will be blocked by that call to Wait and unable to process the continuation. Without the continuation, the task won’t complete, so the call to Wait will never return—deadlock!

Await and Application.Run, a chicken and an egg problem: I mentioned earlier there was a subtle problem. I described that when you call await, the default behavior is to continue execution on the UI thread, which is what I need here. However, the infrastructure for that isn’t set up when I first call await, because the appropriate code hasn’t run yet!

SynchronizationContext.Current is the key to this behavior: When calling await, the infrastructure captures the value of SynchronizationContext.Current, and uses that to post the continu­ation; that’s how it continues on the UI thread. The synchronization context is set up by Windows Forms or WPF when it starts running the message loop. Inside of StartAsync, that hasn’t happened yet: If you examine SynchronizationContext.Current in the beginning of StartAsync, you’ll see it’s null. If there’s no synchronization context, await will post the continuation to the thread pool instead, and because that’s not going to be the UI thread, it’s not going to work.

The WPF version will hang outright, but, as it turns out, the Windows Forms version will “accidentally” work. By default, Windows Forms sets up the synchronization context when the first control is created—in this case, when I construct m_mainForm (this behavior is controlled by WindowsFormsSynchronizationContext.AutoInstall). Because “await InitializeAsync” occurs after I create the form, I’m OK. Were I to put an await call before creating m_mainForm, however, I’d have the same problem. The solution is to set up the synchronization context myself in the beginning, as follows:

SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext());

Exception Handling

Almost there! But I still have another lingering problem at the root of the application: If InitializeAsync raises an exception, the program doesn’t handle it. The programStart task object will contain the exception information, but nothing is being done with it and so my application will be hung in a sort of purgatory. If I could “await StartAsync,” I could catch the exception in Main, but I can’t use await, because Main isn’t async.

This illustrates the second problem with async void: There’s no way to properly catch exceptions thrown by an async void method because the caller has no access to the task object. (So when should you use async void? Typical guidance says async void should be limited mostly to event handlers. The March 2013 article I mentioned before discusses this, too; I recommend reading it to get the most out of async/await.)

Under normal circumstances, TaskScheduler.UnobservedException deals with tasks that raise exceptions that aren’t subsequently handled. The problem is, it’s not guaranteed to run. In this situation, it almost certainly won’t: the task scheduler detects unobserved exceptions when such a task is finalized. Finalization happens only when the garbage collector runs. The garbage collector runs only when it needs to satisfy a request for more memory.

You may see where this is going: In this case, an exception will result in the application sitting around doing nothing, so it won’t request more memory, so the garbage collector won’t run. The effect is that the app will hang. In fact, that’s why the WPF version hangs if you don’t specify the synchronization context: the WPF window constructor throws an exception because a window is being created on a non-UI thread, and then that exception goes unhandled. A final piece, then, is to deal with the programStart task, and add a continuation that will run in case of error. In this case, it makes sense to quit if the application can’t initialize itself.

I can’t use await in Main because it isn’t async, but I can create a new async method solely for the purpose of exposing (and handling) any exceptions thrown during the asynchronous startup: It will consist only of a try/catch around an await. Because this method will be handling all exceptions and not throwing any new ones, it’s another of the limited cases where async void makes sense:

Of course, as usual, there’s a subtle issue (if async/await makes things easier, you can imagine how hard it used to be). I said earlier that typically, when an async method reaches a call to await, it returns, and the remainder of that method runs as a continuation. In some cases, though, the task can complete synchronously; if that’s the case, the execution of the code doesn’t get broken up, which is a performance benefit. If that happens here, though, it means that the HandleExceptions method will run in its entirety and then return, and Application.Run will follow it: In that case, if there is an exception, now the call to Application.Exit will occur before the call to Application.Run, and it won’t have any effect.

What I want to do is force HandleExceptions to run as a continuation: I need to make sure that I “fall through” to Application.Run before doing anything else. This way, if there’s an exception, I know that Application.Run is already executing, and Application.Exit will properly interrupt it. Task.Yield does just that: It forces the current async code path to yield to its caller, and then resume as a continuation.

privatestaticasyncvoid HandleExceptions(Task task)
{
try
{
// Force this to yield to the caller, so Application.Run will be executingawait Task.Yield();
await task;
}
...as before

In this case, when I call “await Task.Yield”, HandleExceptions will return and Application.Run will execute. The remainder of HandleExceptions will then be posted as a continuation to the current SynchronizationContext, which means it will be picked up by Application.Run.

Incidentally, I think Task.Yield is a good litmus test for understanding async/await: If you understand the use of Task.Yield, then you probably have a solid understanding of how async/await works.

The Payoff

Now that everything is working, it’s time to have some fun: I’m going to show how easy it is to add a responsive splash screen without running it on a separate thread. Fun or not, having a splash screen is quite important if your application doesn’t “start” right away: If the user launches your application and doesn’t see anything happen for several seconds, that’s a bad user experience.

Starting a separate thread for a splash screen is inefficient, and it’s also clunky—you have to marshal all the calls properly between threads. Providing progress information on the splash screen is therefore difficult, and even closing it requires a call to Invoke or the equivalent. Moreover, when the splash screen finally does close, usually it doesn’t properly give focus to the main form, because it’s impossible to set the ownership between the splash screen and the main form if they’re on different threads. Compare that to the simplicity of the asynchronous version, shown in Figure 8.

Wrapping Up

I have shown how to apply an object-oriented design to your application’s startup code—whether Windows Forms or WPF—so it can easily support asynchronous initialization. I’ve also shown how to overcome some subtle problems that can come from an asynchronous startup process. As for actually making your initialization asynchronous, I’m afraid you’re on your own for that, but you’ll find some guidance at msdn.com/async.

Enabling the use of async and await is just the start. Now that Program is more object-oriented, other features become more straightforward to implement. I can process command-line argu­ments by calling an appropriate method on the Program class. I can have the user log in before showing the main window. I can start the app in the notification area without showing any window at startup. As usual, an object-oriented design provides the opportunity to extend and reuse functionality in your code.

Mark Sowulmay in fact be a software simulation written in C# (so folks speculate). A devoted .NET developer since the beginning, Sowul shares his wealth of architecture and performance expertise in .NET and Microsoft SQL Server via his New York consulting business, SolSoft Solutions. Reach him at mark@solsoftsolutions.com, and sign up for his occasional e-mails on software insights at eepurl.com/_K7YD.

Thanks to the following Microsoft technical experts for reviewing this article: Stephen Cleary and James McCaffrey