The final design pattern that I'll discuss in this article is the asynchronous pattern used throughout the framework. Obviously, calling methods on a class asynchronously executes them on a separate thread that frees up the main thread, allowing it to respond to user input or go on to do other work. The .NET Framework makes calling methods asynchronously easy by handling the thread management and providing the underlying infrastructure through delegates.
The interesting aspect of this model is that it is client-driven. This means that clients determine whether to call a method synchronously or asynchronously, either using methods provided by the server class (with the class doing the work) or through an asynchronous delegate. For example, classes in the framework such as FileStream provide Begin and End methods inherited from Stream that are used on either end of an asynchronous call in addition to the standard methods that provide the functionality. In the case of FileStream, this means that it supports both BeginRead and BeginWrite methods that can be used asynchronously, in addition to Read and Write methods that are used synchronously. Typical examples of where Begin and End methods already exist in the framework include the proxy class created when an XML Web Service is referenced in VS .NET and when dealing with network IO, remoting, and messaging. It should be noted that calling XML Web Services asynchronously can be particularly effective because network problems or problems with the Web service may prevent a quick turnaround so that the client code can continue execution.
However, if the server class does not support Begin and End methods, you can still call one of its methods asynchronously by simply creating and invoking a delegate. Although a complete discussion of delegates is beyond the scope of this article, delegates are simply type-safe function pointers used to call a method on another object. Delegates also form the basis for events in .NET. It turns out that delegates expose an Invoke method that is used to call the function that the delegate points to, in addition to calling BeginInvoke and EndInvoke to transparently support invoking delegates asynchronously.
Whether you're using server classes that expose Begin and End methods or calling a method asynchronously through a delegate, the pattern is basically the same. First, the caller creates a delegate of type AsyncCallback that points to the method that will be called when the asynchronous operations finishes. For example, if you want to write data to a file on a separate thread using the FileStream class, you would create an asynchronous delegate in VB .NET like this:
Dim writePhoto As New AsyncCallback(AddressOf WritePhotoCallback)
Note that, in VB .NET, you would use the AddressOf operator to reference the method, whereas, in C#, you would simply pass the name of the method to the constructor.
Next, the client code invokes the asynchronous operation and passes it the delegate either through the Begin method exposed by the server class or through the BeginInvoke method if you're working with a class that does not expose them. In this case, the FileStream class exposes a BeginWrite method that can be called directly:
Dim fs As New FileStream("myfile.gif") fs.BeginWrite(byteBuffer, 0, byteBuffer.Length, writePhoto, fs)
In this case, the byteBuffer variable is simply an array of type Byte that holds binary data that represents a photo. At this point, the thread that called BeginWrite is free to perform other operations as the execution of the Write method continues on a separate thread managed by the CLR. When the operation completes, the WritePhotoCallback method is called through the delegate, as shown in Listing 3.
Listing 3: Using a Callback
Public Sub WritePhotoCallback(ByVal ar As IAsyncResult) Dim fs As FileStream ' Used to get the current thread ID ' Dim ThreadID As Integer = Thread.CurrentThread.GetHashCode() ' Console.WriteLine(ar.CompletedSynchronously) ' Get the FileStream fs = CType(ar.AsyncState(), FileStream) ' Finish the writing and close the stream fs.EndWrite(ar) fs.Close() End Sub
In Listing 3, you'll notice that the WritePhotoCallback method must accept one parameter of type IAsyncResult. The IAsyncResult interface exposes several methods that can be called to determine whether the operation completed asynchronously, to retrieve the state passed to the Begin method, and even to poll or wait for the completion of the operation. In this case, the CompletedSynchronously property is called to determine whether the operation actually completed synchronously because the server class can make the decision to run the operation synchronously if it will complete very quickly (for example, the FileStream class will complete all IO operations of less than 64KB synchronously). The AsyncState property is used to retrieve the original FileStream to which the data was written. After the FileStream has been retrieved, the EndWrite method is called to end the operation. Note that the ID of the thread on which the operation completes can be determined using the static CurrentThread method of the Thread class.
Although the pattern discussed so far is the one that you'll most often use with asynchronous operations, the Begin methods can be called without the AsyncCallback delegate by passing in a null value (Nothing in VB .NET). In this case, you'll need to either poll for the completion of the operation using the IsCompleted method of the IAsyncResult interface returned from the Begin method, simply call the End method that will block the current thread until the operation completes, or use the WaitHandle returned from the AsyncWaitHandle property of the IAsyncResult interface to wait for completion. In the first and third cases, you'll then need to explicitly call the End method.