Asynchronous Processing with Delegates
A second major use of delegates in .NET is for handling asynchronous processing. One of the benefits of using delegates for asynchronous processing is that they can be used both in conjunction with .NET Remoting (discussed in Chapter 8, "Building Components,") to facilitate communication between processes and machines and with code that resides in the same application domain. This common programming model allows you to write code that is location transparent as well.
Delegates are appropriate for asynchronous calls because they were designed with BeginInvoke and EndInvoke methods that allow the client to invoke a method asynchronously and then catch its results respectively. You can think of BeginInvoke and EndInvoke as partitioning the execution of a method from a client's perspective. To specify which procedure to call upon completion of the asynchronous method, you use the AsyncCallback object and IAsyncResult interface from the System namespace. When using delegates with the asynchronous classes and interfaces in the System namespace, the CLR provides the underlying services needed to support this programming model including a thread pool and the ability to call objects that reside inside synchronized contexts such as those using COM+ services. (We'll explore more about thread support in the CLR in Chapter 11, "Accessing System Services," as well.) One of the side benefits of this integration is to make it fairly simple to create multi-threaded applications in VB.NET.
VB 6.0 developers will recall that because the VB 6.0 runtime was single-threaded, it was very difficult to create any asynchronous code without resorting to using the Win32 API directly (which led to unstable code that was tricky to debug) or tricking the COM library into creating an object in a new single-threaded apartment. Needless to say, neither technique was particularly attractive.
To understand the pattern for asynchronous programming in .NET, consider a method of the Registration class that is used to send reminder e-mail notifications to all students who are to attend a class the following week. This RemindStudent method might take some time to complete because it must read a list of students from the database, construct an e-mail message for each one, send the e-mail, and update the database appropriately. For the client application's thread to avoid being blocked when the method is called, the method can be called using a delegate (in this case often termed an asynchronous delegate).
Because the .NET asynchronous model allows the client to decide whether the called code will run asynchronously, the code for the RemindStudent method does not have to be written explicitly to handle asynchronous calls. You can simply declare it and write the code as if it were synchronous as follows:
Public Function RemindStudent(ByVal pDaysAhead As Integer, _ ByRef pErrors() as String) As Boolean ' Read from the database and send out the email notification End Sub
Note that the method requires an argument to specify how many days in advance the e-mail should be sent out and returns an array of strings containing any error messages. However, the client code must significantly change. The code in Listing 4.8 is used to call the RemindStudent method.
Listing 4.8Code used to implement asynchronous programming using delegates.
delegate Delegate Function RemStudentCallback(ByVal pDaysAhead As Integer, _ ByRef pErrors() as String) As Boolean ' Async' Client code inside a class or module ' Declare variables Dim temp() As String Dim intDays As Integer = 7 ' Create the Registration class and the delegate Dim objReg As Registration = New Registration() Dim StudCb As RemStudentCallback = New RemStudentCallback(AddressOf _ objReg.RemindStudent) ' Define the AsyncCallback delegate Dim cb As AsyncCallback = New AsyncCallback(AddressOf RemindResult) ' Can create any object as the state object Dim state As Object = New Object() ' Asynchronously invoke the RemindStudent method on objReg StudCb.BeginInvoke(intDays, temp, cb, state) ' Do some other useful work Public Sub RemindResult(ByVal ar As IAsyncResult) Dim strErrors() As String Dim StudCb As RemStudentCallback Dim flResult As Boolean Dim objResult As AsyncResult ' Extract the delegate from the AsyncResult objResult = CType(ar, AsyncResult) StudCb = objResult.AsyncDelegate ' Obtain the result flResult = StudCb.EndInvoke(strErrors, ar) ' Output results End Sub
To begin, notice that the client code first creates a new instance of the Registration class as normal. However, it then creates a new RemStudentCallback delegate and places in it the address of the RemindStudent method of the newly created object instance (objReg). Once again, the delegate must have the same signature as the RemindStudent method.
Now the code must create an address that can be called back into upon completion of the RemindStudent method. To do this, a system delegate class called AsyncCallback is instantiated and passed the address of a procedure called RemindResult. You'll notice that RemindResult must accept one argument: an object that supports the IAsyncResult interface.
After the callback is set up, the RemindStudent method is actually invoked using the BeginInvoke method of the delegate that was created earlier (StudCb). The BeginInvoke method accepts the same arguments as the delegate it was instantiated as in addition to the delegate that contains the callback and an object in which you can place additional state information required by the method.
At this point, your code can continue on the current execution path. The delegate will invoke the RemindStudent method (on a separate thread if in the same application) and when finished will call the procedure defined in the delegate passed to BeginInvoke (in this case, the RemindResult). As mentioned previously, this procedure accepts an argument of type IAsyncResult. To retrieve the instance of the delegate RemStudentCallback from it, you first need to convert it to an object of type AsyncResult using the CType function. The AsyncDelegate property of the resulting AsyncResult object contains the reference to the RemStudentCallback delegate. The return value and any arguments passed ByRef are then captured using the return value and arguments passed to the EndInvoke method of the delegate.