Encapsulate asynchronous functionality directly into your business objects.
The .NET Framework facilitates calling object methods asynchronously through the use of delegates. You may already know how to do this using helper code, but there is a cleaner and much cooler way of packaging this kind of functionality right inside your business objects.
This article assumes a basic knowledge of delegates, the .NET Framework's type-safe version of C++ function pointers.
You can avoid waiting for your calls to complete by using an asynchronous delegate in the conventional manner to call an operation on its own thread.
Part of good component writing involves designing and developing components with the idea that developers other than you will use them. A good practice is to use conventions that are familiar and, if possible, industry standard. In this article, you will learn how to build asynchronous functionality directly into your business objects using a method-naming scheme already in use by asynchronous delegates, asynchronous Web services, and various other asynchronous classes throughout the .NET Framework. In the end, your business objects will be powerful, scalable, and follow a familiar .NET pattern.
Before you design these new objects, let's review how to call an object's method using an asynchronous delegate. First, set up a simple class to serve as a business object and that will evolve throughout this article.
The Business Object
Call this class FileFunctions and give it one method: MoveFiles. This method receives two strings as its arguments: sourceFolder and destinationFolder. Quite simply, the method accesses the directory location identified by the sourceFolder argument, and moves all files within to the directory identified by the destinationFolder argument. The return variable for this method is a Boolean that indicates success or failure of this operation. The actual business functionality will not be described or listed here.
In C#:
public bool MoveFiles(string sourceFolder,
string destinationFolder)
{
// Functionality for file moving.
return true;
}
In VB .NET:
Public Function MoveFiles( _
ByVal sourceFolder As String, _
ByVal destinationFolder As String)
As Boolean
' Functionality for file moving.
Return True
End Function
To perform this function in a normal synchronous fashion, instantiate the class and set a Boolean variable to the method call, sending in the required arguments. In fact, you can do this all in one line.
In C#:
bool b_Success =
new FileFunctions().MoveFiles(
"c:\\BeforeFolder",
"c:\\AfterFolder");
In VB .NET:
Dim b_Success As Boolean = _
New FileFunctions().MoveFiles( _
"c:\\BeforeFolder ", _
"c:\\AfterFolder")
This works just fine, except for the fact that the calling program just sits there until this operation is complete and a success or failure flag has been returned. So let's avoid this wait by using an asynchronous delegate in the conventional manner to call this operation on its own thread.
Conventional Asynchronous Calling: A Refresher Course
First, make sure you import the two namespaces you will need: System, for the AsyncCallback delegate and System.Runtime.Remoting.Messaging, for the IAsyncResult interface and AsyncResult object.
Invoke makes a normal, or synchronous call, and BeginInvoke makes an asynchronous call and receives a couple of extra arguments, conforming to a naming convention used throughout the .NET Framework
Next, declare a delegate that defines the signature of the methods it will call. In this case, you're just worried about the one method, MoveFiles.
In C#:
public delegate bool MoveFilesDelegate(
string origin,
string destination);
In VB .NET:
Public Delegate Function MoveFilesDelegate( _
ByVal origin As String, _
ByVal destination As String) _
As Boolean
I named the arguments of the delegate differently than in the actual method for two reasons. First, they can be called whatever you wish to call them; the delegate simply defines a signature, so the important part here is the data types, not the argument names. Second, you may want to build upon what you learn in this article and use this delegate for something else involving file moving. So origin and destination may not necessarily define folder locations, but they may very well be database tables, or FTP stores, or anything else for that matter.
From the client application, code the plumbing that takes care of firing the method asynchronously. First, instantiate the business object, as if you were going to make a normal synchronous call to any of its methods. Then instantiate the delegate into a variable and give it the method that you want to call with it. Remember, this method must have the same signature and return variable that the delegate defined.
In C#:
FileFunctions o_MyFunctions = new FileFunctions();
MoveFilesDelegate o_MyDelegate = new
MoveFilesDelegate(o_MyFunctions.MoveFiles);
In VB .NET:
Dim o_MyFunctions As FileFunctions = _
New FileFunctions
Dim o_MyDelegate As MoveFilesDelegate = _
New MoveFilesDelegate( _
AddressOf o_MyFunctions.MoveFiles)
More often than not, you want to know when the method you are calling is finished executing. And of course, you may have requested something returned, as in the first example, where MoveFiles was called and returned a Boolean variable indicating success. In order to receive a notification of when the method is finished processing, you have to define a callback method. A callback method can be any method that conforms to the AsyncCallback delegate. The AsyncCallback delegate defines a void method, one that does not return anything, and with one argument of type IAsyncResult. The callback declaration is done in the same manner as you defined the function delegate above.
In C#:
AsyncCallback o_MyCallback =
new AsyncCallback(MoveFilesComplete);
In VB .NET:
Dim o_MyCallback As AsyncCallback = _
New AsyncCallback( _
AddressOf MoveFilesComplete)
Note that as in the previous declaration, you are performing these tasks from a client application. Don't worry, all that will change later. You declared an instance of the AsyncCallback delegate and assigned it to point at a method called MoveFilesComplete. Unlike the MoveFilesDelegate variable you assigned before, this method has no object variable before it, so it resides within the client application itself. This need not be the case every time and you will explore this possibility further later in this article. You'll go on to the method code in a minute. First finish up the task of calling the MoveFiles method asynchronously by invoking it using the delegate you assigned to it.
In C#:
IAsyncResult o_AsyncResult =
o_MyDelegate.BeginInvoke(
"c:\\Folder1", "c:\\Folder2",
o_MyCallback, null);
In VB .NET:
Dim o_AsyncResult As IAsyncResult = _
o_MyDelegate.BeginInvoke( _
"c:\\Folder1", "c:\\Folder2", _
o_MyCallback, Nothing)
Notice that the first two arguments of this call are the arguments that the MoveFiles method is expecting. BeginInvoke automatically adjusts to accommodate however many arguments are expected by the method it is invoking. The next argument is the object you instantiated using the AsyncCallback delegate, which serves as a function pointer to MoveFilesComplete. The last argument is known as AsyncState and can contain anything you wish to put in it. This variable can later be retrieved from the callback routine.
The BeginInvoke method returns an implementation of the IAsyncResult interface. This object can be used to check the status of the asynchronous call to see whether or not it is still processing, as well as to block processing of the current thread (the thread that set up the asynchronous call) and wait for the completion of the invoked method. If you need to perform functionality like that, scope this object at the class level so you can access it outside the method that invoked the asynchronous call.
There are a couple of items to note about how you kicked off the asynchronous process. Upon executing this statement, the MoveFiles method fires and receives the arguments this line of code sends into it. The MoveFiles method fires and runs on a new thread so the client application where you executed it can regain control immediately. You don't have to wait until MoveFiles is finished processing: you have made an asynchronous call to MoveFiles.
The other important item of interest is the name of the method you just used, BeginInvoke. The delegate you declared also has a method called Invoke. You can use BeginInvoke to fire off the MoveFiles routine, but the call will not be made asynchronously. Instead you set a return variable, Boolean in this case, to the Invoke method and send only the arguments that MoveFiles is expecting. In this case, the client application waits until the processing that MoveFiles performs is finished before going to the next line of code.
I'm not discussing synchronous delegate calls in much detail here; the reason I brought it up is to point out the naming conventions used. Invoke makes the normal, or synchronous call, and BeginInvoke makes the asynchronous call and receives a couple of extra arguments. This naming convention is used throughout the .NET Framework to differentiate between synchronous and asynchronous invocation, and you will adopt it for your own business objects later in this article.
Other areas where this naming convention is used are in sockets programming and Web services. When you open a socket in a networking application, you can use a Receive method to receive the contents of a socket's buffer synchronously, or you can use a BeginReceive method to initiate the retrieval of a socket's buffer asynchronously. Later, you can use a callback method to wrap things up. Explaining this further is beyond the scope of this article, but you can see the resemblance.
Web services also use this naming convention for any methods you define as Web methods. Let's say you create a Web service with two Web methods called GetWeatherInfo and GetStockInfo. When you compile the Web service and consume it from a client application, the proxy object automatically adds four more methods to your service: BeginGetWeatherInfo, EndGetWeatherInfo, BeginGetStockInfo, and EndGetStockInfo. The "End" methods in both the sockets and Web service examples conform to the AsyncCallback delegate signature.
Now take a look at the callback method (which also conforms to the AsyncCallback delegate), MoveFilesComplete, and its contents.
In C#:
private void MoveFilesComplete(IAsyncResult ar)
{
MoveFilesDelegate o_MyDelegate =
(MoveFilesDelegateOld)((AsyncResult)ar).
AsyncDelegate;
bool b_Success =
o_MyDelegate.EndInvoke(ar);
// Perform any further functionality.
}
In VB .NET:
Private Sub MoveFilesComplete( _
ByVal ar As IAsyncResult)
Dim o_MyDelegate As MoveFilesDelegate = _
CType( _
CType(ar, AsyncResult).AsyncDelegate, _
MoveFilesDelegateOld)
Dim b_Success As Boolean = _
o_MyDelegate.EndInvoke(ar)
' Perform any further functionality.
End Sub
The first thing to do in the callback method is to obtain an instance of the delegate variable that you declared in order to fire off the method in the first place. Remember o_MyDelegate? It's actually stored in the ar argument. The rather complex-looking line of code used to retrieve it actually breaks up into very simple pieces.
The line of code that retrieves the original delegate variable simply casts the ar variable as an AsyncResult object in order to obtain the AsyncDelegate property, and then casts the results as a MoveFilesDelegate. You need an instance of the original delegate so you can properly close the asynchronous call and retrieve a return variable, which is done in the second line of code. By calling EndInvoke and sending the ar variable you received in the callback routine, you close up the processing on the delegate and retrieve the Boolean return variable. A Boolean was the return type of the MoveFiles method as you designed it. However, in calling it using a delegate, you do not set anything to the method; instead you invoke it and then retrieve its results by way of the callback routine.
Asynchronous Calling, New and Improved
You've seen how to asynchronously invoke an object's method using a delegate and also how to retrieve the results, whenever they may be available, from a callback routine. You have accomplished this from a client application. If you'd like, you can distribute the FileFunctions object as a component to anyone you like, but it would be up to them to write the plumbing code needed to make asynchronous calls to any of its methods.
Now you're going to learn how to encapsulate most of this into the business object itself. This allows you to distribute the business object as a component and provides the methods needed to invoke the MoveFiles routine, or any other for that matter, asynchronously. You add all this built-in functionality using the standard naming conventions mentioned earlier, which are already in use throughout the .NET Framework. Yes, you guessed it; you will be adding two new methods to the FileFunctions object: BeginMoveFiles and EndMoveFiles. The main advantage of this naming convention is that it is, for the most part, self-explanatory. When you distribute your business object, the reasons for having MoveFiles, BeginMoveFiles, and EndMoveFiles methods will be apparent to anyone who has worked with asynchronous delegates in the past.
So let's get started. The first thing you need to do is declare the delegate as in the previous examples:
In C#:
public delegate bool MoveFilesDelegate(
string origin,
string destination);
In VB .NET:
Public Delegate Function MoveFilesDelegate( _
ByVal origin As String, _
ByVal destination As String) _
As Boolean
This statement is identical to before; I show it again here because it may be located differently this time. You are assuming that the FileFunctions class may be in a stand-alone component so that it may be distributed later. The delegate declaration must now be accessible to this class without relying on an outside component, so you need to make sure that it is declared somewhere in the component in which this class resides. Keep in mind, a delegate declaration is still made outside the scope of a class. The code for the complete component is in Listing 1 (C#) and Listing 2 (VB .NET).
The next thing to do is add a BeginMoveFiles method to the class. This method has a return type of IAsyncResult, and conforms to the signature you saw in the BeginInvoke method previously. The signature was comprised of the two arguments MoveFiles receives, the AsyncCallback routine and the AsyncState object. Take a look at the method in its entirety and then I'll dissect it piece by piece. Whether you remember the method of asynchronous calling from this article or were already familiar with asynchronous delegate use, the code in this method will make a lot of sense to you.
In C#:
public IAsyncResult BeginMoveFiles(
string sourceFolder,
string destinationFolder,
AsyncCallback callback,
object asyncState)
{
MoveFilesDelegate o_MyDelegate =
new MoveFilesDelegate(this.MoveFiles);
return o_MyDelegate.BeginInvoke(
sourceFolder, destinationFolder,
callback, asyncState);
}
In VB .NET:
Public Function BeginMoveFiles( _
ByVal sourceFolder As String, _
ByVal destinationFolder As String, _
ByVal callback As AsyncCallback, _
ByVal asyncState As Object) _
As IAsyncResult
Dim o_MyDelegate As MoveFilesDelegate = _
New MoveFilesDelegate( _
AddressOf MoveFiles)
Return o_MyDelegate.BeginInvoke( _
sourceFolder, destinationFolder, _
callback, asyncState)
End Function
Of course, it looks familiar. Look at the first line of code (after the method declaration). The delegate object is instantiated just like before but with one difference. In the first example, you instantiated FileFunctions and then wired the MoveFiles method into the delegate declaration. The MoveFiles method is not in an instantiated object; instead the MoveFiles method is within this class itself.
The second line invokes the delegate with the ever-popular asynchronous launcher method, BeginInvoke. As before, the signature includes the MoveFiles arguments, the callback routine, and the generic AsyncState object. Note that the variable used to identify the callback came to this function as an argument. Unlike the first example, you do not have to declare an AsyncCallback delegate instance for this routine, because the variable already comes into this method as one. You will see how when I demonstrate the client application's code. The entire instance of the BeginInvoke method is returned to the caller as an implementation of the IAsyncResult interface.
As you might have guessed, the BeginMoveFiles method is what gets called from a client application, and you'll see how later. Now you have to declare the EndMoveFiles method, which is not the callback but will get used by the callback. The callback is still defined by the client and resides with the client. The EndMoveFiles routine cleans up the delegate when the client calls it from its callback, or from any other function that wants to block the thread and wait for the asynchronous call to finish. In order to wrap up the asynchronous delegate call, you need access to the original delegate used. Let's see the code first.
In C#:
public bool EndMoveFiles(IAsyncResult ar)
{
MoveFilesDelegate o_MyDelegate =
(MoveFilesDelegate)((AsyncResult)ar).
AsyncDelegate;
return o_MyDelegate.EndInvoke(ar);
}
In VB .NET:
Public Function EndMoveFiles( _
ByVal ar As IAsyncResult) As Boolean
Dim o_MyDelegate As MoveFilesDelegate =
CType( _
CType(ar, AsyncResult).AsyncDelegate, _
MoveFilesDelegate)
Return o_MyDelegate.EndInvoke(ar)
End Function
The first thing to notice is that the signature for this method starts with the return variable the same type as that of the original MoveFiles method. You will also notice that the only argument is the IAsyncResult object. You are maintaining a naming and signature convention that matches not only the BeginInvoke and EndInvoke, which are part of standard delegates, but also all the custom asynchronous calling functionality used throughout the .NET Framework. There's that long complex line of code again; the one that retrieves our original delegate.
Finally you call the EndInvoke method and send to it the IAsyncResult object that was passed in, at the same time capturing whatever variable was returned and in turn sending that back to whatever called EndMoveFiles (you'll see that in a minute).
True to .NET Framework asynchronous standards, EndMoveFiles returns the data type of the method that was asynchronously called in the first place, and it receives as an argument only the IAsyncResult object. The actual callback routine that you passed into BeginMoveFiles will be defined by the client application and, as the first example, must still adhere to the signature defined by the AsyncCallback delegate.
You now have a method of kicking off the MoveFiles process asynchronously from within the object itself, and you have a method of closing the asynchronous process, also in the object itself. This wraps up the object's functionality; let's go back to the client application and see how to use the new methods to perform the asynchronous call.
In the first example, the client application made all the delegate declarations and invoked them in conjunction with an instance of the FileFunction class. Now you are going to use solely the FileFunction class to handle everything. Take a look at how the client application uses the new-and-improved FileFunction class. (Listing 3 (C#) and Listing 4 (VB .NET)) shows the complete client code as it appears in a button click event on a Windows form.)
In C#:
FileFunctions o_MyFunctions = new FileFunctions();
IAsyncResult o_AsyncResult =
o_MyFunctions.BeginMoveFiles(
"c:\\Folder1", "c:\\Folder2",
new AsyncCallback(MoveFilesComplete_NewWay),
null);
In VB .NET:
Dim o_MyFunctions As FileFunctions = _
New FileFunctions
Dim o_AsyncResult As IAsyncResult = _
o_MyFunctions.BeginMoveFiles( _
"c:\\Folder1", "c:\\Folder2", _
New AsyncCallback( _
AddressOf MoveFilesComplete_NewWay), _
Nothing)
As you can see, there is much less delegate code?or at least less "raw" delegate code. The calls made are specific to the b class and the appearance given on the component is a clean and encapsulated design. In fact, it is not mere appearance; the design is functionally much cleaner. It now also resembles a standard .NET Framework component. Ok, let's take this code apart.
The first thing to do is instantiate the FileFunctions class. You can't get around doing that. Instead of declaring a delegate here and wiring the MoveFiles method as before, use the new method that you built into FileFunctions. The first arguments are those that correspond to the original MoveFiles method, followed by the callback method and the AsyncState argument left as null. Also notice that, as before, you are returning an IAsyncResult implementation. If you want to use this return object, scope its declaration at the class level so you can use it outside of this method.
The callback argument is an instantiation of the AsyncCallback delegate wired to the callback method, just like before, only this time you did it in one line of code. This is the reason you did not have to instantiate the AsyncCallback delegate in the BeginMoveFiles method of the business object, but instead used just this callback delegate (received as an argument) directly into the BeginInvoke of the delegate. Let's look at the callback method.
In C#:
private void MoveFilesComplete(
IAsyncResult ar)
{
FileFunctions o_FileFunctions =
new FileFunctions();
bool b_Success =
o_FileFunctions.EndMoveFiles(ar);
// Perform any further functionality.
}
In VB .NET:
Private Sub MoveFilesComplete( _
ByVal ar As IAsyncResult)
Dim o_FileFunctions As FileFunctions = _
New FileFunctions
Dim b_Success As Boolean = _
o_FileFunctions.EndMoveFiles(ar)
' Perform any further functionality.
End Sub
Notice that this method conforms to the signature of the AsyncCallback delegate. In the first example, you retrieved the original delegate from the ar variable and called the EndInvoke method. Here, you just instantiate another instance of the FileFunctions class, call the EndMoveFiles method, and send the IAsyncResult argument you received into it. The IAsyncResult argument contains all the information pertaining to the asynchronous process. Remember from the EndMoveFiles code this is where you retrieve the original delegate and call EndInvoke. This entire delegate plumbing now happens behind the scenes. You need to retrieve the original FileFunctions instance because it was that object that kicked off a new thread, so it is that object you need to close and properly housekeep that thread.
Conclusion
As you can see, it's not that complicated, and you can repeat this methodology for any other methods for which you want to add an asynchronous wrapper. With this technique, you can provide your component's users with more complete and robust objects that also conform to .NET Framework naming and usage conventions.