CHAPTER 19 MULTITHREADED, PARALLEL, AND ASYNC PROGRAMMING
reading bogus data, which is sure to give way to extremely odd (and very hard to find) bugs, which are
even harder to replicate and debug.
Atomic operations, on the other hand, are always safe in a multithreaded environment. Sadly, there
are very few operations in the .NET base class libraries that are guaranteed to be atomic. Even the act of
assigning a value to a member variable is not atomic! Unless the .NET Framework 4.5 SDK documentation
specifically says an operation is atomic, you must assume it is thread-volatile and take precautions.
The Role of Thread Synchronization
At this point, it should be clear that multithreaded programs are in themselves quite volatile, as
numerous threads can operate on the shared resources at (more or less) the same time. To protect an
application’s resources from possible corruption, .NET developers must make use of any number of
threading primitives (such as locks, monitors, and the [Synchronization] attribute or language keyword
support) to control access among the executing threads.
Although the .NET platform cannot make the difficulties of building robust multithreaded
applications completely disappear, the process has been simplified considerably. Using types defined
within the System.Threading namespace, the .NET 4.0 and higher Task Parallel Library (TPL), and the
.NET 4.5 C# async and await language keywords, you are able to work with multiple threads with
minimal fuss and bother.
Before diving into the System.Threading namespace, the TPL, and the C# async and await keywords,
we will begin by examining how the .NET delegate type can be used to invoke a method in an
asynchronous manner. While it is most certainly true that under .NET 4.5, the new C# async and await
keywords offer a simpler alternative to asynchronous delegates, it is still important that you know how to
interact with code using this approach (trust me; there is a ton of code in production that makes use of
asynchronous delegates).
A Brief Review of the .NET Delegate
Recall that a .NET delegate is essentially a type-safe, object-oriented, function pointer. When you define
a .NET delegate type, the C# compiler responds by building a sealed class that derives from
System.MulticastDelegate (which in turn derives from System.Delegate). These base classes provide
every delegate with the ability to maintain a list of method addresses, all of which may be invoked at a
later time. Consider the following BinaryOp delegate, first defined in Chapter 10:
// A C# delegate type.
public delegate int BinaryOp(int x, int y);
Based on its definition, BinaryOp can point to any method taking two integers (by value) as
arguments and returning an integer. Once compiled, the defining assembly now contains a full-blown
class definition that is dynamically generated when you build your project, based on the delegate
declaration. In the case of BinaryOp, this class looks more or less like the following (shown in pseudocode):
public sealed class BinaryOp : System.MulticastDelegate
{
public BinaryOp(object target, uint functionAddress);
public int Invoke(int x, int y);
public IAsyncResult BeginInvoke(int x, int y,
AsyncCallback cb, object state);
public int EndInvoke(IAsyncResult result);
}
699