I’ve been doing some reading and experimenting with the new C# 5 Async CTP, available for download from here. I guess that the Async Framework will be a part of .NET 5, although I haven’t found a formal document stating so. There are also some blogs released in the past few months by MS’ people that share information about this new feature. I’ll try to summarize it as I understand it for now, and specifically relate to my findings with regards to ASP.NET.
C# 5 Async Framework seems to focus on easing asynchronous programming. It seems like MS has decided to help us developers write asynchronous code without having to deal with the technical aspect of things, mainly callbacks or thread management, and focus instead on the actual logic we need to implement. The basic idea is that a developer may write code “more or less” in a synchronous fashion, and for the Async Framework to do the rest. In my opinion, this does not mean that a developer won’t have to understand the concept of async programming, nor does it mean that a developer will cease using other “orthodox” methods of async programming.
Having said that, here is an example based on MS Async Whitepapers of how it looks. First, lets review a normal nowadays synchronous method for downloading images and saving them on the local machine, using ASP.NET WebForms Page:
- Lines 16-19 represent Urls of images for download.
- Lines 22-25 iterate over the Urls, calling a synchronous method per Url.
- Lines 30-37 is the method which downloads the given Url synchronously using a simple WebClient (I’ve omitted IDispose handling in this post for simplicity).
The Async CTP allows us to transform this code into asynchronous code by performing minor changes:
For the moment we’ll skip the Page_Load implementation and go directly to the downloading method (lines 27-34). We can see very clearly that the implementation remains almost identical to the original method. However, the Async CTP has some new keywords in C# 5 which are used here, that instruct the compiler to compile our code differently behind the scenes. These are async and await:
- async: This tells the compiler to compile our method differently, so that it uses Tasks and callbacks to run. Although async is mandatory for using awaits, basically its useless without having any awaits in it. This means that if an asynced method has no await instructions within, the method runs synchronously as usual. Writing async in a method doesn’t cause it to be run on a different thread at all (as you might have expected). All it does is to instruct the compiler to auto-generate hidden classes behind the scenes in order to support awaits.
- await is what actually tells the compiler to auto-generate code using Tasks in order to run asynchronously. An async method’s code is executed on the same thread it was called upon, until it gets to the first await. Every await (starting with the first one) executes and returns immediately. Once again, this may be confusing with nowadays Wait( ) methods which usually block threads. This isn’t the case here. await does the opposite – it causes the compiler to auto-generate code which runs the code asynchronously, while preserving the context of the method. The rest of the method is compiled as a callback of the async Task. When the Task has completed, the code resumes execution using the synchronization context “right where it left off”. The Async CTP Whitepaper is very clear on this: “At first glance the await keyword looks like it blocks the thread until the task is complete and the data is available, but it doesn’t. Instead it signs up the rest of the method as a callback on the task, and immediately returns. When the awaited task eventually completes, it will invoke that callback and thus resume the execution of the method right where it left off!”
Now that we understand what async and await are, we can understand lines 27-34 better. The highlighted changes are:
- Line 27 contains two method signature changes: async was added in order to “tell” the compiler that this method is going to use the Async framework; The Task return class is the real deal here, because it’s what allows the calling code to query the status of the async task.
- Line 30 also contains two changes: await performs an async call to DownloadDataTaskAsync, which returns, that’s right, a Task. In the Whitepaper’s terminology, we “await the task”.
- Lines 31-33 is compiled as a callback for the Task. This code will not be executed until line 30 completes.
With minor changes to the code, our previously synchronous method has become asynchronous! What’s quite amazing here is that the code still looks as if it is synchronous. This means that in the future, it’s supposed to be much easier to write code asynchronously. Let’s go over the calling code now:
- Line 14 has a new async which “tells” the compiler we’re using Async framework in this method.
- Line 22 uses await, which basically what starts the async operation and awaits completion, but what’s more important is that we use a parallel new feature, TaskEx.WhenAll( ), which waits till all the tasks have completed (sort of a Thread.Join for all the Tasks). The TaskEx is the temp CTP class and the WhenAll method is expected to be a part of the Task class upon release.
- We have to await TaskEx.WhenAll( ) or the Stopwatch will be stopped immediately (line 23). Nevertheless, the Tasks will end as expected during the Page’s life-cycle – this will be explained later on.
- Lines 23-24 are compiled as callbacks and will not be called till line 22 finishes awaiting the task.
One more thing we need to add here, is that just like in ASP.NET 2, running a code asynchronously requires us to have an Async=”true” in the @Page directive. Failure to do so will result in a: “Asynchronous operations are not allowed in this context. Page starting an asynchronous operation has to have the Async attribute set to true and an asynchronous operation can only be started on a page prior to PreRenderComplete event.“, which hints us not only at the Async=”true” directive, but also that the PreRenderComplete is somehow involved. I’ll discuss this later on.
Let’s have a quick view in Reflector at how this looks (we’ll focus on the Page_Load await):
- Notice that the MyForm page contains hidden elements which were auto-generated by the compiler. There’s now a <Page_Load>d__1 class which is used for the asynced Page_Load method (and there’s also a <AsyncDownloadFile>d__6 class for the asynced AsyncDownloadFile method). As you can see, the actual Page_Load method looks entirely different than the original source code.
- This statement is basically the what will used to perform the callback when the operation completes.
- MoveNext() actually starts execution (see below).
The above code shows us the auto-generated MoveNext() of the <Page_Load>d__1 class:
- This is our original code, initializing the Urls to be downloaded, as well as the Stopwatch which times the operation (the code which refers to GetExecutingAssembly was inserted by me in order to be able to easily detect where the compiled assembly is, and to Reflect it – so just ignore it.)
- That’s the awaiter.
- OnComplete, calls the callback (remember item #2 in the previous Reflector window?), which is the MoveNext() again, till the awaiter’s IsCompleted method returns true.
- This is the actual callback code – Stop the Stopwatch and display the elapsed time.
As you can see, our original code was split into 2 parts, one to be executed prior to the await, and the rest as callback code.
Finally, let’s understand the life-cycle of things. I’ve added some code to log the different operations and thread creations. I also added two life-cycle events (PreRender and PreRenderComplete), because I wanted to learn how the life-cycle handles awaits and asyncs. This is a screenshot of the result:
Short explanation: The “Thread number” column is what’s important. It hints at whenever a new thread was assigned to handle the executing code, which actually means we can better understand the life-cycle of ASP.NET in an async scenario. Here are some highlights:
- The first thread, which is assigned by ASP.NET to handle the request, is the one stated as “Page_Load thread”. As we can see, this thread executes all the code which runs just prior to the first await. Each await executes and returns immediately, and this can explain why both “before awaits” are on the same thread.
- Important: OnPreRender wasn’t blocked. So now we know that asynced code which returned from the awaits continues unblocked and does not wait for the Tasks completion in the ASP.NET life-cycle, at least up to PreRender.
- The first AsyncDownloadFile log message shows us that the callback is running on a different thread, as expected. So we know for sure that the async framework has indeed did what it was supposed to do, which it to split our original code into a block which runs on the calling thread, and a callback which runs on a different ThreadPool thread.
- Note that the second AsyncDownloadFile log message shows us that a different thread handled the second url callback. That’s also “as expected”, as different Tasks handled each url.
- As opposed to PreRender, we can see that PreRenderComplete executes after all awaiting Tasks have finished. This will be the behavior even if we remove the await from the TaskEx.WhenAll( ) call. That’s important, because we now understand that ASP.NET is involved in the life-cycle of things and takes care that we won’t finish the Page’s life-cycle before all Tasks are completed. This resembles ASP.NET 2 Async Page pattern (see summary below), and sheds light on the meaning of the detailed Exception specified earlier.
To summarize what we’ve seen so far, using the already existing Task classes, await starts an asynchronous operation and returns immediately. So, the code itself is not blocked at all. The compiler generates code also for the callback, so our code continues only when the Task has completed. In other words, async and await cause the compiler to compile our code into async Tasks and callbacks. ASP.NET integrates well with this environment and “knows” to await all tasks, if you haven’t done it yourself. Needless to say, the asynced version runs in parallel which makes it a lot faster.
If you feel like these async and await keywords are misused, you are welcome to join the arguments which seem to be taking place. As written previously, according to Eric Lippert’s post, developers misinterpret await for a blocking Wait-like operation, whereas it’s exactly the opposite, and tend to believe that writing async in the method signature means that .NET will run it asynchronously. However, as stated in Eric’s blog post, attempts to find better keywords so far have failed.
I find the suggested framework interesting as it will probably allow a somewhat “default” implementation of async programming, and will allow us developers to achieve async solutions more easily.
I think that many developers who know how to develop async programming to some extent, don’t actually do so. I even “dare” to think that this is a more common situation in server side web programming. Unlike Windows Forms development, where using a BackgroundWorker or some other threading solution is usually required in heavy duty tasks, but somewhat trivial because the forms stay in-memory for as long as we require them to, in server side web programming this is quite different. Most ASP.NET developers rely on IIS and ASP.NET to assign requests to threads, and know that if you initiate a multi-threaded operation yourself in ASP.NET, it is a pain. That’s because when you start a thread, the Page doesn’t stay in-memory or wait for it to finish. In Web Forms, the life-cycle doesn’t wait just because a developer opened a new thread in Page_Load or some other Button_Click event handler. The newly created thread will probably complete after the life-cycle has ended and the response was already sent to the client. More over, if you do implement a threading scenario in ASP.NET and block the page life-cycle using a Thread.Join or some other threading blocking mechanism, the actual ASP.NET main thread processing the request is stuck till the new thread has completed, and is not returned to the ThreadPool. That’s why the people at Microsoft have invented Async Pages in ASP.NET 2. This implementation was built on the idea that somewhere in the Page’s life-cycle (after PreRender), an async operation may start, the thread handling the request will be returned to the ThreadPool, and upon completion of the async thread, the life-cycle is resumed using a different ThreadPool thread (returning to the PreRenderComplete in the life-cycle). While this was some sort of a solution, it was still uncomfortable and forced to developer to be bound to a certain step in the life-cycle in order to achieve async in ASP.NET. So, my guess is that most developers chose not to use this solution as well. In short, while client side async programming evolved thanks to Ajax, server side web programming in ASP.NET lacked the proper infrastructure to evolve. Thanks to the Async framework, it’s possible we’ll see more async web server side development.