As an example, consider the class in the following section.
This class factorizes a number into its two prime factors.
// Class the factorizers the number public class PrimeFactorizer { public bool Factorize( int factorizableNum, ref int primefactor1, ref int 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 snippet shows the caller defining a pattern for the invoking Factorize method asynchronously:
// Define the delegate public delegate bool FactorizingCallback( int factorizableNum, ref int primefactor1, ref int primefactor2); // Creating an instance of the Factorizer PrimeFactorizer pf = new PrimeFactorizer(); // Creating a delegate on the Factorize method on the Factorizer FactorizingCallback fd = new FactorizingCallback(pf.Factorize);
When the compiler emits the FactorizingCallback delegate class after parsing its definition at the first line above, it will generate the BeginInvoke and EndInvoke methods in addition to the Invoke method.
class FactorizingCallback : delegate { public bool Invoke( int factorizableNum, ref int primefactor1, ref int primefactor2); // Supplied by the compiler public IAsyncResult BeginInvoke( int factorizableNum, ref int primefactor1, ref int primefactor2, AsyncCallback cb, Object AsyncObject ); // Supplied by the compiler public bool EndInvoke( ref int primefactor1, ref int primefactor2, IAsyncResult ar); }
The following code snippet demonstrates the client-side programming model for invoking the Factorize method asynchronously.
The class receives the results of the Async call on a callback.
public delegate bool FactorizingCallback( int factorizableNum, ref int primefactor1, ref int primefactor2); // Class that receives a callback when the the results are available public class ProcessFactorizedNumber { private int _ulNumber; public ProcessFactorizedNumber(int number) { _ulNumber = number; } // Note the qualifier one-way. [OneWayAttribute()] public void FactorizedResults(IAsyncResult ar) { int factor1=0, factor2=0; // Extract the delegate from the AsyncResult FactorizingCallback fd = (FactorizingCallback)ar.AsyncObject; // Obtain the result fd.EndInvoke(ref factor1, ref factor2, ar); // Output results Console.WriteLine("On CallBack: Factors of {0} : {1} {2}", _ulNumber, factor1, factor2); } }
// Async Variation 1 // The ProcessFactorizedNumber.FactorizedResults callback // is called when the call completes. public void FactorizeNumber1() { // Client code PrimeFactorizer pf = new PrimeFactorizer(); FactorizingCallback fd = new FactorizingCallback(pf.Factorize); // Async Variation 1 int factorizableNum = 1000589023, temp=0; // Create an instance of the class which is going // to called when the call completes ProcessFactorizedNumber fc = new ProcessFactorizedNumber(factorizableNum); // Define the AsyncCallback delegate AsyncCallback cb = new AsyncCallback(fc.FactorizedResults); // Can stuff any object as the state object Object state = new Object(); // Asynchronously invoke the Factorize method on pf IAsyncResult ar = fd.BeginInvoke( factorizableNum, ref temp, ref temp, cb, state); // // Do some other useful work //. . . }
// Async Variation 2 // Waits for the result public void FactorizeNumber2() { // Client code PrimeFactorizer pf = new PrimeFactorizer(); FactorizingCallback fd = new FactorizingCallback(pf.Factorize); // Async Variation 1 int factorizableNum = 1000589023, temp=0; // Create an instance of the class which is going // to called when the call completes ProcessFactorizedNumber fc = new ProcessFactorizedNumber(factorizableNum); // Define the AsyncCallback delegate AsyncCallback cb = new AsyncCallback(fc.FactorizedResults); // Can stuff any object as 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 results Console.WriteLine("Sequencial : Factors of {0} : {1} {2}", factorizableNum, factor1, factor2); } }
Note that if the ProcessFactorizedNumber is a context-bound class that requires synchronized / thread- affinity context, the callback is dispatched through the context dispatcher infrastructure. In other words, the callback itself may execute asynchronously with respect to its caller for such contexts. That is precisely the semantics of the one-way qualifier on method signatures. It means that any such method call may 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 second time with the same IAsyncResult is undefined.
The server splits asynchronous operation into its two logical parts: the part that takes input from the client and kicks off the asynchronous operation and the part that supplies results of the asynchronous operation to the client. In addition to the input needed for the async operation, the first part also takes an AsyncCallback delegate to be called when the asynchronous operation is completed. The first part returns a waitable object that implements IAsyncResult interface used by the client to determine the status of the asynchronous operation. The Server typically also utilizes 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.
Options available to client for initiating asynchronous operations:
Options available to the client for completing asynchronous operations are: