Asynchronous Programming Design Pattern

The following code example demonstrates a server class that factorizes a number.

Public Class PrimeFactorizer   
   Public Function Factorize(factorizableNum As Long, ByRef primefactor1   
            As Long, ByRef primefactor2 As Long) As Boolean
      primefactor1 = 1
      primefactor2 = factorizableNum
      
      ' Factorize using a low-tech approach.
      Dim i As Integer
      For i = 2 To factorizableNum - 1
         If 0 = factorizableNum Mod i Then
            primefactor1 = i
            primefactor2 = factorizableNum / i
            Exit For
         End If
      Next i
      If 1 = primefactor1 Then
         Return False
      Else
         Return True
      End If
   End Function
End Class
[C#]
public class PrimeFactorizer
{
   public bool Factorize(long factorizableNum,  
            ref long primefactor1,
            ref long primefactor2)
   {
      primefactor1 = 1;
      primefactor2 = factorizableNum;

      // Factorize using a low-tech approach.
      for (int i=2;i<factorizableNum;i++)
      {
         if (0 == (factorizableNum % i))
         {
            primefactor1 = i;
            primefactor2 = factorizableNum / i;
            break;
         }
      }
      if (1 == primefactor1 )
         return false;
      else
         return true   ;
   }
}

The following code example shows a client defining a pattern for asynchronously invoking the Factorize method from the PrimeFactorizer class in the previous example.

' Define the delegate.
Delegate Function FactorizingAsyncDelegate(factorizableNum As Long, ByRef    
      primefactor1 As Long, ByRef primefactor2 As Long)
End Sub
' Create an instance of the Factorizer.
Dim pf As New PrimeFactorizer()

' Create a delegate on the Factorize method on the Factorizer.
Dim fd As New FactorizingDelegate(pf.Factorize)
[C#]
// Define the delegate.
public delegate bool FactorizingAsyncDelegate(long factorizableNum,  
      ref long primefactor1,
      ref long primefactor2);

// Create an instance of the Factorizer.
PrimeFactorizer pf = new PrimeFactorizer();

// Create a delegate on the Factorize method on the Factorizer.
FactorizingDelegate fd = new FactorizingDelegate(pf.Factorize);

The compiler will emit the following FactorizingAsyncDelegate class after parsing its definition in the first line of the previous example. It will generate the BeginInvoke and EndInvoke methods.

Public Class FactorizingAsyncDelegate
   Inherits Delegate

   Public Function Invoke(factorizableNum As Long, ByRef primefactor1 As 
         Long, ByRef primefactor2 As Long) As Boolean
   End Function
   
   ' Supplied by the compiler.
   Public Function BeginInvoke(factorizableNum As Long, ByRef primefactor1 
         As Long, ByRef primefactor2 As Long, cb As AsyncCallback,
         AsyncState As Object) As       
         IasyncResult
   End Function
   
   ' Supplied by the compiler.
   Public Function EndInvoke(ByRef primefactor1 As Long, ByRef 
         primefactor2 As Long, ar As IAsyncResult) As Boolean
   End Function
[C#]
public class FactorizingAsyncDelegate : Delegate
{
   public bool Invoke(ulong factorizableNum,  
         ref ulong primefactor1, ref ulong primefactor2);

   // Supplied by the compiler.   
   public IAsyncResult BeginInvoke(ulong factorizableNum,  
            ref unsigned long primefactor1,
            ref unsigned long primefactor2, AsyncCallback cb, 
            Object AsyncState);

   // Supplied by the compiler.   
   public bool EndInvoke(ref ulong primefactor1,
               ref ulong primefactor2, IAsyncResult ar);
}

The interface used as the delegate parameter in the following code example is defined in the .NET Framework Class Library. For more information, see IAsyncResult Interface.

Delegate Function AsyncCallback(ar As IAsyncResult)

' Returns true if the asynchronous operation has been completed.         
Public Interface IasyncResult
      ' Handle to block on for the results.
      ReadOnly Property IsCompleted() As Boolean
         ' Get accessor implementation goes here.
      End Property 

      ' Caller can use this to wait until operation is complete.
      ReadOnly Property AsyncWaitHandle() As WaitHandle
         ' Get accessor implementation goes here.
      End Property 
      
      ' The delegate object for which the async call was invoked.
      ReadOnly Property AsyncObject() As [Object]
         ' Get accessor implementation goes here.
      End Property 
      
      ' The state object passed in through BeginInvoke.
      ReadOnly Property AsyncState() As [Object]
         ' Get accessor implementation goes here.
      End Property 
      
      ' Returns true if the call completed synchronously.
      ReadOnly Property CompletedSynchronously() As Boolean
         ' Get accessor implementation goes here.
      End Property
   End Interface
[C#]
public delegate AsyncCallback (IAsyncResult ar);

public interface IAsyncResult
{
   // Returns true if the asynchronous operation has completed.
   bool IsCompleted { get; }

   // Caller can use this to wait until operation is complete.
   WaitHandle AsyncWaitHandle { get; }

   // The delegate object for which the async call was invoked.
   Object AsyncObject { get; }

   // The state object passed in through BeginInvoke.
   Object AsyncState { get; }

   // Returns true if the call completed synchronously.
   bool CompletedSynchronously { get; }
}

Note that the object that implements the IAsyncResult Interface must be a waitable object and its underlying synchronization primitive should be signaled after the call is canceled or completed. This enables the client to wait for the call to complete instead of polling. The runtime supplies a number of waitable objects that mirror Win32 synchronization primitives, such as ManualResetEvent, AutoResetEvent and Mutex. It also supplies methods that support waiting for such synchronization objects to become signaled with "any" or "all" semantics. Such methods are context-aware to avoid deadlocks.

The Cancel method is a request to cancel processing of the method after the desired time-out period has expired. Note that it is only a request by the client and the server is recommended to honor it. Further, the client should not assume that the server has stopped processing the request completely after receiving notification that the method has been canceled. In other words, the client is recommended to not destroy resources such as file objects, as the server might be actively using them. The IsCanceled property will be set to true if the call was canceled and the IsCompleted property will be set to true after the server has completed processing of the call. After the server sets the IsCompleted property to true, the server cannot use any client-supplied resources outside of the agreed-upon sharing semantics. Thus, it is safe for the client to destroy the resources after the IsCompleted property returns true.

The Server property returns the server object that provided the IAsyncResult.

The following code example demonstrates the client-side programming model for invoking the Factorize method asynchronously.

public class ProcessFactorizeNumber
{
   private long _ulNumber;

   public ProcessFactorizeNumber(long number)
   {
      _ulNumber = number;
   }

   [OneWayAttribute()]
   public void FactorizedResults(IAsyncResult ar)
   {
      long factor1=0, factor2=0; 
      
      // Extract the delegate from the AsynchResult.
      FactorizingAsyncDelegate fd = 
         (FactorizingAsyncDelegate) ((AsyncResult)ar).AsyncDelegate;
      // Obtain the result.
      fd.EndInvoke(ref factor1, ref factor2, ar);

      // Output the results.
      Console.Writeline("On CallBack: Factors of {0} : {1} {2}",
                        _ulNumber, factor1, factor2);
   }
}

// Async Variation 1.
// The ProcessFactorizeNumber.FactorizedResults callback function
// is called when the call completes.
public void FactorizeNumber1()
{
   // Client code.
   PrimeFactorizer pf = new PrimeFactorizer();
   FactorizingAsyncDelegate fd = new FactorizingAsyncDelegate (pf.Factorize);

   long factorizableNum = 1000589023, temp=0; 

   // Create an instance of the class that
   // will be called when the call completes.
   ProcessFactorizedNumber fc = 
      new ProcessFactorizedNumber(factorizableNum);
   // Define the AsyncCallback delegate.   
   AsyncCallbackDelegate cb = new AsyncCallback(fc.FactorizedResults);
   // Any object can be the state object.
   Object state = new Object();

   // Asynchronously invoke the Factorize method on pf.
   // Note: If you have pure out parameters, you do not need the 
   // temp variable.
   IAsyncResult ar = fd.BeginInvoke(factorizableNum, ref temp, ref temp,  
                        cb, state); 

// Proceed to do other useful work.

// Async Variation 2.
// Waits for the result.
// Asynchronously invoke the Factorize method on pf.
// Note: If you have pure out parameters, you do not need 
// the temp variable.
public void FactorizeNumber2()
{
   // Client code.
   PrimeFactorizer pf = new PrimeFactorizer();
   FactorizingAsyncDelegate fd = new FactorizingAsyncDelegate (pf.Factorize);

   long factorizableNum = 1000589023, temp=0;
   // Create an instance of the class 
   // to be called when the call completes.
   ProcessFactorizedNumber fc = 
            new ProcessFactorizedNumber(factorizableNum);

   // Define the AsyncCallback delegate.
   AsyncCallback cb = new AsyncCallback(fc.FactorizedResults);

   // Any object can be the state object.
   Object state = new Object();

   // Asynchronously invoke the Factorize method on pf.
   IAsyncResult ar = fd.BeginInvoke(factorizableNum, ref temp, ref temp, 
                        null, null); 

   ar.AsyncWaitHandle.WaitOne(10000, false);

   if(ar.IsCompleted)
   {
      int factor1=0, factor2=0;

      // Obtain the result.
      fd.EndInvoke(ref factor1, ref factor2, ar);

      // Output the results.
      Console.Writeline("Sequential : Factors of {0} : {1} {2}", 
                     factorizableNum, factor1, factor2);
   }
}

Note that if FactorizeCallback is a context-bound class that requires synchronized or thread-affinity context, the callback function is dispatched through the context dispatcher infrastructure. In other words, the callback function itself might execute asynchronously with respect to its caller for such contexts. These are the semantics of the one-way qualifier on method signatures. Any such method call might execute synchronously or asynchronously with respect to caller, and the caller cannot make any assumptions about completion of such a call when execution control returns to it.

Also, calling EndInvoke before the asynchronous operation is complete will block the caller. Calling it a second time with the same AsyncResult is undefined.

Summary of Asynchronous Programming Design Pattern

The server splits an asynchronous operation into its two logical parts: the part that takes input from the client and starts the asynchronous operation, and the part that supplies the results of the asynchronous operation to the client. In addition to the input needed for the asynchronous operation, the first part also takes an AsyncCallbackDelegate object to be called when the asynchronous operation is completed. The first part returns a waitable object that implements the IAsyncResult interface used by the client to determine the status of the asynchronous operation. The server typically also uses the waitable object it returned to the client to maintain any state associated with asynchronous operation. The client uses the second part to obtain the results of the asynchronous operation by supplying the waitable object.

When initiating asynchronous operations, the client can either supply the callback function delegate or not supply it.

The following options are available to the client for completing asynchronous operations:

  • Poll the returned IAsyncResult object for completion.
  • Attempt to complete the operation prematurely, thereby blocking until the operation completes.
  • Wait on the IAsyncResult object. The difference between this and the previous option is that the client can use time-outs to periodically take back control.
  • Complete the operation inside the callback function routine.

One scenario in which both synchronous and asynchronous read and write methods are desirable is the use of file input/output. The following example illustrates the design pattern by showing how the File object implements read and write operations.

Public Class File   
   ' Other methods for this class go here.

   ' Synchronous read method.
   Function Read(buffer() As [Byte], NumToRead As Long) As Long 
   
   ' Asynchronous read method.
   Function BeginRead(buffer() As [Byte], NumToRead As Long, cb As   
            AsyncCallbackDelegate) As IAsyncResult   
   Function EndRead(ar As IAsyncResult) As Long 
   
   ' Synchronous write method.
   Function Write(buffer() As [Byte], NumToWrite As Long) As Long   
   
   ' Asynchrnous write method.
   Function BeginWrite(buffer() As [Byte], NumToWrite As Long, cb As   
            AsyncCallbackDelegate) As IAsyncResult 
   Function EndWrite(ar As IAsyncResult) As Long 
End Class
[C#]
public class File
{
   // Other methods for this class go here.

   // Synchronous read method.
   long Read(Byte[] buffer, long NumToRead);

   // Asynchronous read method.
   IAsyncResult BeginRead(Byte[] buffer, long NumToRead, 
            AsyncCallbackDelegate cb);
   long EndRead(IAsyncResult ar);

   // Synchronous write method.
   long Write(Byte[] buffer, long NumToWrite);

   // Asynchrnous write method.
   IAsyncResult BeginWrite(Byte[] buffer, long NumToWrite, 
            AsyncCallbackDelegate cb);

   long EndWrite(IAsyncResult ar);
}

The client cannot easily associate state with a given asynchronous operation without defining a new callback function delegate for each operation. This can be fixed by making Begin methods, such as BeginWrite, take an extra object parameter that represents the state and which is captured in the IAsyncResult.

See Also

Design Guidelines for Class Library Developers | Asynchronous Execution | IAsyncResult Interface