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