Delegate And Async Programming C# (AsyncCallback And Object State)


Previous article
I'm using the same example as in my previous article, so the following are all the terms and code from 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 as a reminder, bp is the instance of a BinaryOP delegate that points to a simple Add() method that performs 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 the 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 a 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 a Boolean variable in two different threads which is not thread safe. However to use such a shared variable the very good rule of thumb is to ensure that data that can be shared among multiple threads is locked down.

Now when the secondary thread completes the Add() operation, the question should be where am I 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 does not satisfy 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:

    AsyncCallback Delegate in C#

    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#.
     

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

    The AsyncResult class

    The AddComplete() method of AsyncCallback takes the AsyncResult class object compatible to IAsyncResult as an argument. The static AsyncDelegate property of the 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:

AsyncResult class in C#

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 the primary thread to the secondary thread.
Because the prototype is System.Object you can pass any object as a parameter, as long as the callback knows what type of object to expect.
Now let us 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 the 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:

argument of BeginInvoke in C#

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

Next Recommended Readings