Thursday, 21 July 2011

Delegate and Async Programming Part II - C# (AsyncCallback and Object State)


In the previous post we discussed about the use of delegates to call the methods asynchronously then we talked about Synchronization of threads in Multithreading environment. In the previous code example we used BeginInvoke() method make the Async call with below parameters:
Since I’m using the same example so just mentioning all the terms and code line we used in the previous example:
        //Simple delegate declaration
        public delegate int BinaryOp(int x, int y);

        //An Add() method that do some simple arithamtic operation
        public int Add(int a, int b)
        {
            Console.WriteLine("Add() running on thread {0}", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(500);
            return (a + b);
        }

            BinaryOp bp = new BinaryOp(Add);
            IAsyncResult iftAr = bp.BeginInvoke(5, 5, null, null);

Just to remind that bp is the instance of BinaryOP delegate that pointing to simple Add() methods that perform some basic arithmetic operations. We passed the arguments to BeginInvoke() with some null values. Now instead of using null in the parameters you’ll use objects which are part of the signature of BeginInvoke() method of dynamic class of delegate BinaryOp.
BeginInvoke(int x, int y, AsyncCallback cb, Object state);
Let’s discuss what the last two parameters are for:
The AsyncCallback Delegate
Rather than polling the delegate to determine whether an asynchronous call is completed using the properties of IAsyncResult object, It would be more efficient to have the secondary inform the calling thread when  the task is finished. When you wish to enable this behavior you need to pass an instance of System.AsyncCallback delegate as a parameter to BeginInvoke(), which up until this point has been NULL.
Note: The Callback method will be called on the secondary thread not on the primary Main() thread.
The method that has to be passed into the AsyncCallback delegate should have void return type and IAsyncResult as a parameter to match the delegate signature.
        //Target of AsyncCallback delegate should match the following pattern
        public void AddComplete(IAsyncResult iftAr)

Note: In the previous Example we used an Boolean variable in two different thread which is not Thread safe. However to use such shared variable the very good rule of thumb is to ensure that data that can be shared among multiple thread is locked down.
Now when secondary thread completes the Add() operation the question should come into your mind  where I’m gonna call the EndInvoke().  Well you can call the EndInvoke in two places.
  1. Inside the main just after the condition where you’re waiting for the secondary thread to complete. But this is not solving the purpose of passing AsyncCallback.
        public delegate int BinaryOp(int x, int y);
        static void Main(string[] args)
        {
            Console.WriteLine("Main() running on thread {0}", Thread.CurrentThread.ManagedThreadId);

            Program p=new Program();
            BinaryOp bp = new BinaryOp(p.Add);
            IAsyncResult iftAr = bp.BeginInvoke(5, 5, new AsyncCallback(p.AddComplete), null);

            while (!iftAr.AsyncWaitHandle.WaitOne(100,true))
            {
                Console.WriteLine("Doing some work in Main()!");
            }
            int result = bp.EndInvoke(iftAr);
            Console.WriteLine("5 + 5 ={0}", result);
            Console.Read();
        }
//An Add() method that do some simple arithamtic operation
        public int Add(int a, int b)
        {
            Console.WriteLine("Add() running on thread {0}", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(500);
            return (a + b);
        }

        //Target of AsyncCallback delegate should match the following pattern
        public void AddComplete(IAsyncResult iftAr)
        {
            Console.WriteLine("AddComplete() running on thread {0}", Thread.CurrentThread.ManagedThreadId);

            Console.WriteLine("Operation completed.");
        }
Output:

Here you can see the AddComplete() is called after the Main() completed its execution. If you are looking deep into the code and find iftAr.AsyncWaitHandle.WaitOne(100,true)) as something alien statement then you should look at the Delegate and Async Programming Part I - C#.

  1. Second and interesting approach is showing the result by calling EndInovke() inside the AddComplete(). Now you should have question here. The instance of BinaryOp delegate is running on the primary thread in the Main() How can we access it to secondary thread to call the EndInvoke()? Then answer is System.Runtime.Remoting.Messaging.AsyncResult class.

The AsyncResult class
The AddComplete() method of AsyncCallback takes the AsyncResult class object compatible to IAsyncResult as argument. The static AsyncDelegate property of AsyncResult class returns a reference to the original asynchronous delegate that was created elsewhere, and which is accessible through the IAsyncResult. Simply just cast the returned object by the AsyncDelegate to BinaryOp.
Update the code in the example remove the lines of EndInvoke from the Main() and let’s call it from the AddComplete() method.
//Target of AsyncCallback delegate should match the following pattern
        public void AddComplete(IAsyncResult iftAr)
        {
            Console.WriteLine("AddComplete() running on thread {0}", Thread.CurrentThread.ManagedThreadId);

            Console.WriteLine("Operation completed.");

            //Getting result
            AsyncResult ar = (AsyncResult)iftAr;
            BinaryOp bp = (BinaryOp)ar.AsyncDelegate;
            int result = bp.EndInvoke(iftAr);

            Console.WriteLine("5 + 5 ={0}", result);
            Console.WriteLine("Message recieved on thread {0}", Thread.CurrentThread.ManagedThreadId);
        }

Now the output will be:

Hope you can see the difference in the output of both the approaches here.

Passing and Receiving Custom State Data using the final argument of BeginInvoke()
Now let’s address the final argument of BeginInvoke() which has been null up to this point. This parameter allows you to pass additional state information from primary thread to secondary thread.
Because the proto type is System.Object so you can pass any object as parameter, as long as the callback knows what type of object to expect.
Now let demonstrate passing a simple string message form the primary thread in the BeginInvoke() and receiving it in the Callback on the secondary thread using the AsyncState property of IAsyncResult incoming parameter.
Now here’s the complete code:
class Program
    {
        //Simple delegate declaration
        public delegate int BinaryOp(int x, int y);
        static void Main(string[] args)
        {
            Console.WriteLine("Main() running on thread {0}", Thread.CurrentThread.ManagedThreadId);

            Program p=new Program();
            BinaryOp bp = new BinaryOp(p.Add);
            IAsyncResult iftAr = bp.BeginInvoke(5, 5, new AsyncCallback(p.AddComplete), "This message is from Main() thread "+Thread.CurrentThread.ManagedThreadId);
            while (!iftAr.AsyncWaitHandle.WaitOne(100,true))
            {
                Console.WriteLine("Doing some work in Main()!");
            }

            Console.Read();
        }

        //An Add() method that do some simple arithamtic operation
        public int Add(int a, int b)
        {
            Console.WriteLine("Add() running on thread {0}", Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(500);
            return (a + b);
        }

        //Target of AsyncCallback delegate should match the following pattern
        public void AddComplete(IAsyncResult iftAr)
        {
            Console.WriteLine("AddComplete() running on thread {0}", Thread.CurrentThread.ManagedThreadId);

            Console.WriteLine("Operation completed.");

            //Getting result
            AsyncResult ar = (AsyncResult)iftAr;
            BinaryOp bp = (BinaryOp)ar.AsyncDelegate;
            int result = bp.EndInvoke(iftAr);

            //Recieving the message from Main() thread.
            string msg = (string)iftAr.AsyncState;
            Console.WriteLine("5 + 5 ={0}", result);
            Console.WriteLine("Message recieved on thread {0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);
        }

    }

Output:

I hope you enjoyed this.. I’m expecting that next time when you call BeginInvoke() you won’t pass NULL values to some useful arguments.
MultiThreading is what your multicore processor is doing!
So Cheers….

No comments:

Post a Comment