This is part 1 in a series on using Task Parallel Library when writing server applications, especially ASP.NET MVC and ASP.NET Web API applications.
- Introduction
- SynchronizationContext
- ContinueWith
- TaskHelpers
Okay, let’s just get this out of the way:
Asynchronous (multi-threaded) programming is not easy.
This warning really has nothing to do with .NET in particular, because it can be quite challenging to do correctly on any framework, but here’s the good news:
Asynchronous programming with .NET 4 is a little easier.
Asynchronous programming with .NET 4.5 is a lot easier.
The Task Parallel Library (or, as we think about it, Task and Task<T>) were introduced in .NET 4. This library dramatically simplified the more complex async patterns from previous versions of .NET (which includes the begin/end pattern as well as the async/completed event pattern).
The async and await keywords available in C# and VB on .NET 4.5 mean you get to consume Task Parallel Library tasks with code that looks linear and synchronous, but is in actuality asynchronous, with none of the callback handlers or thread synchronization stuff getting in the way. For most developers, this means that asynchrony is simple enough to consume without too much trouble; in fact, the Windows team is so confident of this that all the APIs for modern apps in Windows 8 that might “take a while” (read: more than 100ms) are required to be async, with no synchronous counterpart. The Windows team did this so that you won’t inadvertently lock up the UI and cause the app to appear to be unresponsive, although of course you could end up doing this by running some long running process in your own code on the UI thread.
As such, a great deal of the samples for 4.5 using Task<T> are centered around pushing “long running” tasks onto a background thread so you don’t lock up the UI. There are also a decent number of samples that show using the TPL for parallel processing: that is, when you have something which is CPU-bound, and can be split up into discrete tasks, you run many of them in parallel so as to get the best use of modern multi-core CPUs.
There is another category of asynchronous programming, though, that has had very little ink devoted to it: server-side programming; in particular, using Task<T> when writing web applications and web APIs, which yields the real purpose for this blog post:
Asynchronous programming for servers is not the same as clients.
Client asynchrony is all about getting work off the all-important UI thread, so that the UI appears to be response to the user, even when working is going on in the background. On a server, there is no such thing as a background thread vs. a UI thread: every thread in the thread pool is a potential handler for a request, and the client has to wait until all the work is done, regardless of which thread(s) it takes place on.
In the context of servers, the most important thing to remember is that we don’t want to switch threads if at all possible, because a thread context switch can dramatically reduce the scalability of your server. That means whenever you consume a task, you have to be careful to use mechanisms that don’t switch the thread unnecessarily.
In addition, there’s no point in making a task on the server when what you’re doing is fundamentally CPU-bound. There’s no background thread for this work to hide on, so all you’re doing is incurring an expensive thread switch. Where TPL makes sense on servers is when you’re consuming something which is fundamentally async but not CPU bound, which means things like database operations and web service calls.
A final consideration for your server-based TPL code is synchronization contexts. The SynchronizationContext class was introduced in .NET 2.0 as an abstraction around the need for asynchronous code to get its state lined back up to continue correctly functioning. In the case of Windows Forms and WPF, this means ensuring that your post-async code is running on the UI thread so that you can do UI manipulation; in the case of ASP.NET, this means ensuring that your current thread has all the necessary thread-local statics set up (so that calls like HttpContext.Current function correctly).
Further blog posts in this series will talk about how to safely consume Tasks from your server code on .NET 4, and some rather in-depth analysis of the task helper libraries that the ASP.NET team has made to ensure that Task-related code runs in the way that incurs the least amount of performance impact.