Asynchronous Programming

Team-Fly    

 
Application Development Using Visual Basic and .NET
By Robert J. Oberg, Peter Thorsteinson, Dana L. Wyatt
Table of Contents
Chapter 10.  .NET Framework Classes


.NET supports a design pattern for asynchronous programming. This pattern is present in many places in .NET (including I/O operations, as noted earlier and as we will see in Chapter 15). Asynchronous programming provides a way for you to provide a method call without blocking the method caller. Within your own code, the asynchronous model may provide an easier approach than threading, but offers much less control over the synchronization than using synchronization objects.

The Asynchronous Design Pattern

This design pattern is composed of two special methods exposed by a delegate object and the interface IAsyncResult . The two special methods, BeginInvoke and EndInvoke , are emitted directly by the compiler, and not implemented in the class source code, and not inherited from a super class. The compiler emits the equivalent to these two functions for the delegate, as shown in the following code snippet. Note that parameters represents the arbitrary number of parameters defined in the corresponding delegate definition, and ReturnType represents the return type of the corresponding delegate definition.

 Function BeginInvoke(_  parameters..., _  ByVal cb As AsyncCallback, _  ByVal AsyncObject As Object) As IAsyncResult    ... End Function Function EndInvoke(_  parameters..., _  ByVal ar As IAsyncResult) As ReturnType    ... End Function 

For general discussion purposes, "XXX" is sometimes used in referring to the actual method being called asynchronously (i.e., BeginRead / EndRead for the System.IO.FileStream class). The BeginXXX method should pass all input parameters of the synchronous version as well as the AsyncCallback and Object parameters. The EndXXX should have all the output parameters of the synchronous version parameters in its signature. It should return whatever object or value that the synchronous version of the method would return. It should also have an IAsyncResult parameter. A CancelXXX can also be provided if it makes sense.

AsyncCallback is a delegate that represents a callback function.

 Public Delegate Sub AsyncCallback(ByVal ar As IAsyncResult) 

AsyncObject is available from IAsyncResult . It is provided so that in the callback function you can distinguish which asynchronous read the callback was generated by.

The .NET Framework uses this pattern so that the FileStream -synchronous Read method

 Overrides Public Function  Read  (_    ByVal array() As Byte, _    ByVal offset As Integer, _    ByVal count As Integer _) As Integer 

becomes in the asynchronous version

 Overrides Public Function  BeginRead  (_    ByVal array() As Byte, _    ByVal offset As Integer, _    ByVal numBytes As Integer, _    ByVal userCallback As AsyncCallback, _    ByVal stateObject As Object _) As IAsyncResult Overrides Public Function  EndRead  (_    ByVal asyncResult As IAsyncResult _) As Integer 

Any exception thrown from BeginXXX should be thrown before the asynchronous operation starts. Any exceptions from the asynchronous operation should be thrown from the EndXXX method.

IAsyncResult

This interface has four elements:

 Public Interface IAsyncResult    ReadOnly Property AsyncState() As Object    ReadOnly Property AsyncWaitHandle() As WaitHandle    ReadOnly Property CompletedSynchronously() As Boolean    ReadOnly Property IsCompleted() As Boolean End Interface 

IsCompleted is set to true after the server has processed the call. The client can destroy all resources after IsCompleted is set to true. If BeginXXX completed synchronously, CompletedSynchronously is set to true. Most of the time this will be ignored and set to the default value of false. In general, a client never knows whether the BeginXXX method executed synchronously or asynchronously. If the asynchronous operation is not finished, the EndXXX method will block until the operation is finished.

The AsyncWaitHandle returns a WaitHandle that can be used for synchronization. As we discussed previously, this handle can be signaled so that the client can wait on it. Since you can specify a wait time period, you do not have to block forever if the operation is not yet complete.

The AsyncState is the object provided as the last argument in the BeginXXX call. It allows you to differentiate asynchronous reads in the callback.

Using Delegates for Asynchronous Programming

Any developer of .NET objects who wants to provide an asynchronous interface should follow this pattern. Nonetheless, there is no need for most developers to develop a custom asynchronous solution for their objects. Delegates provide a very easy way to support asynchronous operations on any method without any action on the class developer's part. Of course, this has to be done with care because the object was written with certain assumptions about which thread it is running on and its synchronization requirements.

The two Asynch examples use the Customers object from our case study Customer assembly. The first example registers new customers asynchronously and does some processing while waiting for each registration to finish. The second example uses a callback function with the asynchronous processing. In addition to allowing the program to do processing while waiting for the registrations to finish, the callback allows the system to take some asynchronous action for each individual registration.

In the examples, we just print out to the console to show where work could be done. To increase the waiting time to simulate longer processing times, we have put calls to Thread.Sleep() in Customers.RegisterCustomer as well as in the sample programs. Now let us look at the code within the examples.

Suppose the client wants to call the RegisterCustomer method asynchronously. The caller simply declares a delegate with the same signature as the method.

 graphics/codeexample.gif Public Delegate Function RegisterCustomerCbk(_  ByVal firstName As String, _  ByVal LastName As String, _  ByVal EmailAddress As String) As Integer 

You then make the actual method the callback function:

 Dim rcc As RegisterCustomerCbk = _    New RegisterCustomerCbk(_    AddressOf customers.RegisterCustomer) 
Begin/End Invoke

When you declare a delegate, the compiler generates a class with three methods, BeginInvoke , EndInvoke , and Invoke . The BeginInvoke and EndInvoke are type-safe methods that correspond to the BeginXXX and EndXXX methods and allow you to call the delegate asynchronously. The Invoke method is what the compiler uses when you call a delegate. To call RegisterCustomer asynchronously, just use the BeginInvoke and EndInvoke methods.

 Dim rcc As RegisterCustomerCbk = _    New RegisterCustomerCbk(_    AddressOf customers.RegisterCustomer) Dim i As Integer For i = 1 To 4    firstName = "FirstName" + i.ToString()    lastName = "SecondName" + (i * 2).ToString()    emailAddress = i.ToString() + ".biz"    Dim ar As IAsyncResult = rcc.BeginInvoke(_       firstName, lastName, emailAddress, Nothing, Nothing)    While (Not ar.IsCompleted)       Console.WriteLine(_          "Could do some work here while waiting " & _          "to complete.")       ar.AsyncWaitHandle.WaitOne(1, False)    End While    customerId = rcc.EndInvoke(Nothing, Nothing, Nothing, ar)    Console.WriteLine("    Added CustomerId: " + _     customerId.ToString()) Next 

The program waits on the AsyncWaitHandle periodically to see if the registration has finished. If the registration is not completed yet, some work could be done in the interim. If EndInvoke is called before RegisterCustomer is complete, EndInvoke will block until RegisterCustomer is finished.

Asynchronous Callback

Instead of waiting on a handle, you could pass a callback function to BeginInvoke (or a BeginXXX method).

 Dim rcc As RegisterCustomerCbk = _  New RegisterCustomerCbk(_   AddressOf customers.RegisterCustomer) Dim cb As AsyncCallback = New AsyncCallback(_  AddressOf CustomerCallback) Dim objectState As Object Dim ar As IAsyncResult Dim i As Integer For i = 5 To 9    firstName = "FirstName" + i.ToString()    lastName = "SecondName" + (i * 2).ToString()    emailAddress = i.ToString() + ".biz"    objectState = i    ar = rcc.  BeginInvoke  (_     firstName, lastName, emailAddress, cb, objectState) Next Console.WriteLine("Finished registrations...could do some work here.") Thread.Sleep(25) Console.WriteLine("Finished work...waiting to let registrations complete.") Thread.Sleep(1000) 

You then get the results in the callback function with EndInvok e:

graphics/codeexample.gif
 Public Sub CustomerCallback(ByVal ar As IAsyncResult)    Dim customerId As Integer    Dim asyncResult As AsyncResult = ar    Dim rcc As RegisterCustomerCbk = _     asyncResult.AsyncDelegate    customerId = rcc.  EndInvoke  (Nothing, Nothing, Nothing, ar)    Console.WriteLine(_     "    AsyncState: {0} CustomerId {1} added.", _     ar.AsyncState, _     customerId)    Console.WriteLine(_     Could do processing here.")    Return End Sub 

You could do some work when each customer registration was finished.

Threading with Parameters

The asynchronous callback runs on a different thread than the one on which BeginInvoke was called. If your threading needs are simple and you want to pass parameters to your thread functions, you can use asynchronous delegates to do this. You do not need any reference to the Threading namespace. The reference to the Threading namespace in the asynchronous threading example is just for the Thread.Sleep method needed for the purposes of demonstrating asynchronous callback.

The PrintNumbers class in the AsynchThreading example sums the numbers from a starting integer passed to it as an argument to 10 greater than the starting integer. It returns the sum to the caller. PrintNumbers can be used for the delegate defined by Print.

 Public Delegate Function Print(_  ByVal i As Integer) As Integer Class Numbers    Public Function PrintNumbers(_     ByVal start As Integer) As Integer       Dim threadId As Integer = _          Thread.CurrentThread.GetHashCode()       Console.WriteLine(_          "PrintNumbers Id: " + threadId.ToString())       Dim sum As Integer = 0       Dim i As Integer       For i = start To start + 9          Console.WriteLine(i.ToString())          Thread.Sleep(500)          sum += i       Next       Return sum    End Function    ... End Class 

The Main routine then defines two callbacks and invokes them explicitly with different starting integers. It waits until the both of the synchronization handles are signaled. EndInvoke is called on both, and the results are written to the console.

 <MTAThread()> _ Public Shared Sub Main()    Dim threadId As Integer = _       Thread.CurrentThread.GetHashCode()    Console.WriteLine(_       "MainThread Id: " + threadId.ToString())    Dim n As Numbers = New Numbers()    Dim pfn1 As Print = _       New Print(AddressOf n.PrintNumbers)    Dim pfn2 As Print = _       New Print(AddressOf n.PrintNumbers)    Dim ar1 As IAsyncResult = _       pfn1.  BeginInvoke  (0, Nothing, Nothing)    Dim ar2 As IAsyncResult = _       pfn2.  BeginInvoke  (100, Nothing, Nothing)    Dim wh() As WaitHandle = New WaitHandle(1) {}    wh(0) = ar1.AsyncWaitHandle    wh(1) = ar2.AsyncWaitHandle    ' make sure everything is done before ending    WaitHandle.  WaitAll  (wh)    Dim sum1 As Integer = pfn1.  EndInvoke  (ar1)    Dim sum2 As Integer = pfn2.  EndInvoke  (ar2)    Console.WriteLine(_       "Sum1 = " + sum1.ToString() + _       " Sum2 = " + sum2.ToString())    Return End Sub 

Here is the program's output:

 MainThread Id: 72 PrintNumbers Id: 77 0 1 2 3 4 5 6 7 8 9 PrintNumbers Id: 77 100 101 102 103 104 105 106 107 108 109 Sum1 = 45 Sum2 = 1045 

Team-Fly    
Top
 


Application Development Using Visual BasicR and .NET
Application Development Using Visual BasicR and .NET
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 190

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net