Asynchronous Programming

for RuBoard

.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 11 for Web services). Asynchronous programming provides a way for you to provide a method call without blocking the method caller. From the perspective of the client, the asynchronous model is easier to use than threading. It offers much less control over the synchronization than using synchronization objects, however, and the class designer would probably find threading much easier to use.

The Asynchronous Design Pattern

This design pattern is composed of two parts , a set of methods and an interface IAsyncResult . The methods of the pattern are:

 IAsyncResult BeginXXX(inputParams, AsyncCallback cb,                                    Object AsyncObject)  ReturnValue EndXXX(outputParams, IAsyncResult ar); 

As a design pattern, the XXX represents the actual method being called asynchronously (i.e., BeginRead / EndRead for the System.IO.FileStream class). The BeginXXX should pass all input parameters of the synchronous version ( in , in/out , and ref ) as well as the AsyncCallback and AsyncObject parameters. The EndXXX should have all the output parameters of the synchronous version ( ref , out , and in/out ) parameters in its signature. It should return whatever object or value the synchronous version of the method would return. It should also have an IAsyncResult parameter. A CancelXXX can be provided if it makes sense.

The AsyncCallback is a delegate that represents a callback function.

 public delegate void AsyncCallback(IAsyncResult ar); 

The 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 Framework uses this pattern so that the FileStream synchronous Read

 int Read(byte[] array, int offset, int count); 

becomes in the asynchronous version:

 IAsyncResult BeginRead(byte[] array, int offset,                   int numBytes, AsyncCallback userCallback,                                         object stateObject);  int EndRead(IAsyncResult asyncResult); 

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

IAsyncResult is returned by a BeginXXX method (such as BeginRead). This interface has four elements:

 interface IAsyncResult  {     public boolean IsCompleted();     public boolean CompletedSynchronously;     public WaitHandle AsyncWaitHandle;     public Object AsyncState;  } 

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

The AsyncWaitHandle returns a WaitHandle that can be used for synchronization. As 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. Since it is contained in the IAsyncResult passed to the callback function, examining its value would allow you to determine which BeginXXX caused this particular instance of 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 the 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.

 public delegate int RegisterCustomerCbk(string firstName,                      string LastName, string EmailAddress); 

You then make the actual method the callback function:

 RegisterCustomerCbk rcc = new            RegisterCustomerCbk(customers.RegisterCustomer); 
Begin/End Invoke

When you declare a delegate, the compiler generates a class with a constructor and 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 implicitly uses when you call a delegate. [12] To call RegisterCustomer asynchronously just use the BeginInvoke and EndInvoke methods.

[12] If you open the executable from the DelegateAccount example in Chapter 5 in ILDASM, you can observe this. The NotifyCallback class has the BeginInvoke , EndInvoke , and Invoke methods defined. If you look at the Withdraw method for Account , you will notice that the C# line notifyDlg(balance) has been transformed to instance void NotifyCallback::Invoke(valuetype [mscorlib]System.Decimal).

 RegisterCustomerCbk rcc = new             RegisterCustomerCbk(customers.RegisterCustomer);  for(int i = 1; i < 5; i++)  {    firstName = "FirstName" + i.ToString();    lastName = "SecondName" + (i * 2).ToString();    emailAddress = i.ToString() + ".biz";    IAsyncResult ar = rcc.BeginInvoke(firstName, lastName,                                  emailAddress, null, null);     while(!ar.IsCompleted)    {      Console.WriteLine("Could do some work here while wait ing for customer registration to complete.");    ar.AsyncWaitHandle.WaitOne(1, false);    }    customerId = rcc.EndInvoke(ar);    Console.WriteLine("    Added CustomerId: " +                          customerId.ToString());  } 

The program waits on the AsyncWaitHandle periodically to see if the registration has finished. If it has not, 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).

 RegisterCustomerCbk rcc = new            RegisterCustomerCbk(customers.RegisterCustomer);  AsyncCallback cb = new AsyncCallback(CustomerCallback);  object objectState;  IAsyncResult ar;  for(int i = 5; i < 10; i++)  {    firstName = "FirstName" + i.ToString();    lastName = "SecondName" + (i * 2).ToString();    emailAddress = i.ToString() + ".biz";    objectState = i;    ar = rcc.BeginInvoke(firstName, lastName,                       emailAddress, cb, objectState);  }  Console.WriteLine      ("Finished registrations...could some do 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:

 public void CustomerCallback(IAsyncResult ar)  {    int customerId;     AsyncResult asyncResult = (AsyncResult)ar;    RegisterCustomerCbk rcc =             (RegisterCustomerCbk)asyncResult.AsyncDelegate;    customerId = rcc.EndInvoke(ar);      Console.WriteLine("    AsyncState: {0} CustomerId {1}  added.", ar.AsyncState, customerId);      Console.WriteLine("      Could do processing here.");    return;  } 

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

Threading with Parameters

The asynchronous callback runs on a different thread from 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 that namespace in the AsynchThreading example is just for the Thread.Sleep method needed for demo purposes.

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

 public delegate int Print(int i);  public class Numbers  {    public int PrintNumbers(int start)    {      int threadId = Thread.CurrentThread.GetHashCode();      Console.WriteLine("PrintNumbers Id: " +                                       threadId.ToString());      int sum = 0;      for (int i = start; i < start + 10; i++)      {        Console.WriteLine(i.ToString());        Thread.Sleep(500);        sum += i;      }    return sum;    }  } 

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.

 Numbers n = new Numbers();  Print pfn1 = new Print(n.PrintNumbers);  Print pfn2 = new Print(n.PrintNumbers);  IAsyncResult ar1 = pfn1.BeginInvoke(0, null, null);  IAsyncResult ar2 = pfn2.BeginInvoke(100, null, null);  WaitHandle[] wh = new WaitHandle[2];  wh[0] = ar1.AsyncWaitHandle;  wh[1] = ar2.AsyncWaitHandle;  // make sure everything is done before ending  WaitHandle.WaitAll(wh);  int sum1 = pfn1.EndInvoke(ar1);  int sum2 = pfn2.EndInvoke(ar2);  Console.WriteLine("Sum1 = " + sum1.ToString() +                              " Sum2 = " + sum2.ToString()); 

Here is the program's output:

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


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

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