This is the final part of the series that started with...
Callback and Multicast delegates
One more Event
Asynchronous Callback - Way 1 - BeginInvoke > EndInvoke
Asynchronous Callback - Way 2 - BeginInvoke > AsyncWaitHandle.WaitOne(x) > EndInvoke > AsynWaitHandle.Close()
Asynchronous Callback - Way 3 - BeginInvoke > Poll for result's IsCompleted > EndInvoke
In this scenario, if we go with the husband-wife-kiddo analogy, I will need to introduce another guy! No no no... please don't misunderstand me... he is the just the driver
So, now the husband drops his wife at the mall. And instead of coming back to pick her up, he simply says... honey, I am going to the office. When you are done shopping, call the driver (+91-97415-xxxxx) and he would take you home.
NOTE >
- As soon as the main thread calls the asynchronous method, his part is done
- The callback method is executed on the ThreadPool thread
- The call to BeginInvoke (so far... it has been something like... caller.BeginInvoke(25, out threadId, null, null);) would now have the 3rd parameter as an AsyncCallback which contains the callback method name.
- The 4th parameter takes an object which your callback method might like to use
- The ThreadPool threads are background threads. This means that they won't keep the application running in case the main thread ends. Thus, the main thread has to be alive for long enough to ensure that the background threads are done processing.
Let's take a look at the code (and read the comments to get a better understanding!).
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics; using System.Runtime.Remoting.Messaging; namespace EventAndDelegateDemo { //The delegate must have the same signature as the method. In this case, //we will make it same as TortoiseMethod public delegate string TortoiseCaller(int seconds, out int threadID); public class TortoiseClass { // The method to be executed asynchronously. public string TortoiseMethod(int seconds, out int threadID) { threadID = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("The slow method... executes...on thread {0}", Thread.CurrentThread.ManagedThreadId); for (int i = 0; i < 5; i++) { Thread.Sleep(seconds / 5 * 1000); Console.WriteLine("The async task is going on thread # {0}", Thread.CurrentThread.ManagedThreadId); } return String.Format("I worked in my sleep for {0} seconds", seconds.ToString()); } } //Now, that we are done with the declaration part, let's proceed to //consume the classes and see it in action //The algorithm would be very simple... // 1. Call delegate's BeginInvoke and pass the callback method's name // 2. Do some work on the main thread // 3. Your callback method is called when the processing completes. // 4. Retrieve the delegate and call EndInvoke which won't be a blocking call this time! public class TortoiseConsumer { static void Main() { //Instantiate a new TortoiseClass TortoiseClass tc = new TortoiseClass(); //Let's create the delegate now TortoiseCaller caller = new TortoiseCaller(tc.TortoiseMethod); //This is a dummy variable since this thread is not supposed to handle the callback anyways!!! int dummy = 0; //Start the asynchronous call with the following parameters... //Parameter 1 = 30 > In my example the tortoise class will now do something for 30 seconds //Parameter 2 = Dummy variable, just to get the output of the threadID //Parameter 3 = new AsyncCallback(CallbackMethod) > This is the method which would be called once the async task is over //Parameter 4 = Object > This is a string which would display the information about the async call IAsyncResult result = caller.BeginInvoke(30, out dummy, new AsyncCallback(CallThisMethodWhenDone), "The call executed on thread {0}, with return value "{1}"."); Console.WriteLine("The main thread {0} continues to execute...", Thread.CurrentThread.ManagedThreadId); //The callback method will use a thread from the ThreadPool. //But the threadpool threads are background threads. This implies that if you comment the line below //you would notice that the callback method is never called, since the background threads can't stop the main //program to terminate! Thread.Sleep(3000); Console.WriteLine("The main thread ends. Change the value 3000 in code to 40000 and see the result"); } //The signature for the call back method must be same System.IAsyncResult delegate. static void CallThisMethodWhenDone(System.IAsyncResult ar) { //To retrieve the delegate we will cast the IAsyncResult to AsyncResult and get the caller AsyncResult result = (AsyncResult)ar; TortoiseCaller caller = (TortoiseCaller)result.AsyncDelegate; //Get the object (in our case it is just a format string) that was passed while calling BeginInvoke! string formattedString = (string)ar.AsyncState; // Define a variable to receive the value of the out parameter. // If the parameter were ref rather than out then it would have to // be a class-level field so it could also be passed to BeginInvoke. //The following variable would take the Thread ID int threadId = 0; //At this point, the threadID won't be a dummy variable as it was in the Main method //We are passing threadID to get the output from the TortoiseMethod string returnValue = caller.EndInvoke(out threadId, ar); //Use the format string to format the output message. Console.WriteLine(formattedString, threadId, returnValue); } } }
There will be more on Delegates and Asynchronous programming going forward.