TopMenu

Delegates and Async Programming Part I - C#



The .Net delegate type is essentially a type-safe, object oriented, function pointer. Lets get into bit more detail about it. When you declare a .Net delegate, the C# compiler responds by building a sealed class that derives from the System.MulticastDelegate with ability to maintain a list of method addresses, all of which may be invoked at time or later time.
Let see an example:
// A C# type delegate
public delegate int BinaryOp(int x, int y);

Based on its definition it can point to any method which matched its signature. After compiling your program the defining assembly will have full blown definition of the dynamically generated BinaryOp class. This class looks more or less like:
    public sealed class BinaryOp : System.MultiCastDelegate
    {
        public BinaryOp(object target, uint functionAddress);
        public void Invoke(int x, int y);
        public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state);
        public int EndInvoke(IAsyncResult result);
    }

The Invoke method
The Invoke method is used to invoke the methods maintained by delegate object in a Synchronous manner. The Invoke() method does not need to directly called in code, but can be indirectly can be triggered indirectly under the hood when applying “normal” method invocation syntax.

The Asynchronous nature of Delegates
If you are new to multithreading, you may want to know what exactly an Asynchronous method invocation is all about. If you are executing a method in a single threaded application you may have to wait until that operation gets completed and then your next operation will start. What if you want to perform some task without waiting for other task. The question therefore is how can you tell a delegate to invoke a method on a separate thread of execution to simulate numerous tasks performing “at the same time?” The good news is that every .Net delegate type is equipped with this capability.

The BeginInvoke() and EndInvoke() Methods
The dynamically generated class for the delegate define two methods BeginInvoke() and EndInvoke().
public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state);
        public int EndInvoke(IAsyncResult result);

The first stack of parameters passed into BeginInvoke() will based on the format of the C# delegate. The final two arguments will always be System.AsynCallBack and System.Object. In the below example I’ll pass null for both. Also note that return value of EndInvoke() is an integer, based on the return type of BinaryOp delegate, while parameter of this method is of type IAsyncResult which is return type of BeginInvoke().

The System.IAsyncResult Interface
The IAsyncResult compatible object returned from the BeginInvoke() is basically a coupling mechanism that allows the calling thread to obtain the asynchronous method invocation at a later time via EndInvoke(). The IAsynResult is defined as follows:
        public interface IAsyncResult
        {
            object AsyncState { get; }
            WaitHandle AsyncWaitHandle { get; }
            bool CompletedSynchronously { get; }
            bool IsCompleted { get; }
        }

Invoking a Method Asynchronously
        public delegate int BinaryOp(int x, int y);

        static void Main(string[] args)
        {

            Console.WriteLine("------ Async Delegate Invocation -------");

            //Print out the ID of the executing thread
            Console.WriteLine("Main() invoked on thread {0}", Thread.CurrentThread.ManagedThreadId);

            //Invoke Add() on a secondary thread. The object required here to access Add() inside static method so don’t have to bother
            BinaryOp b = new BinaryOp(new Program().Add);
            IAsyncResult iftAr = b.BeginInvoke(5, 5, null, null);

            //Do some other work on priamry thread..
            Console.WriteLine("Doing some work in Main()!");

            //Obtain the reault of Add() method when ready
            int result = b.EndInvoke(iftAr);
            Console.WriteLine("5 + 5 is {0}", result);

            Console.ReadLine();
        }

        public int Add(int a, int b)
        {
            //Print out the ID of the executing thread
            Console.WriteLine("Add() invoked on thread {0}", Thread.CurrentThread.ManagedThreadId);
            //wait some time
            Thread.Sleep(500);
            //perform the task
            return (a + b);
        }

Now when you run this program, the output would be something like this:

Here you will notice upon running the application that the “Doing More Work in Main()” message displays immediately, while second thread is Occupied attending to its business.

Synchronizing the calling thread
If you think carefully then you’ll realize that the span time calling BeginInvoke() and EndInvoke() is clearly less than five seconds. Therefore once the “Doing more work in Main()!” prints to console, the calling thread is blocked and waiting for the secondary thread to complete before being able to obtain the result of Add() method. There this is an effective yet another Synchronous call. Agree?
Obviously the meaning of being asynchronously will lose if the calling thread had the potential of being blocked under various circumstances.
Now we’ll use the IAsyncReult compatible objects property ISCompleted to make this call asynchronous.
Update the code in the Main() and see the output:
static void Main(string[] args)
        {

            Console.WriteLine("------ Async Delegate Invocation -------");

            //Print out the ID of the executing thread
            Console.WriteLine("Main() invoked on thread {0}", Thread.CurrentThread.ManagedThreadId);

            //Invoke Add() on a secondary thread
            BinaryOp b = new BinaryOp(new Program().Add);
            IAsyncResult iftAr = b.BeginInvoke(5, 5, null, null);

            while (!iftAr.IsCompleted)
            {
                //Do some other work on priamry thread..
                Console.WriteLine("Doing some work in Main()!");
         Thread.Sleep(100);

            }

            //Obtain the reault of Add() method when ready
            int result = b.EndInvoke(iftAr);
            Console.WriteLine("5 + 5 is {0}", result);

            Console.ReadLine();
        }


You see the more work is done in Main() now. Its fully utilized and asynchronous way of doing it. J
The portion we’ve have added in the Main() can also we written in some interesting way. In Addition the AsyncWaitHandle property of IAsyncResult returns an instance of WaitHandler type, which exposes a method WaitOne(). You can specify the maximum wait time as you are expecting to execute the Add() in this case. If the sepecified amount of time is exceeded, WaitOne() returns false. Now the code would be updated like this:
            while (!iftAr.AsyncWaitHandle.WaitOne(100, true))
            {
                //Do some other work on priamry thread..
                Console.WriteLine("Doing more work in Main()!");
            }
We’ve removed the Thread.Sleep(100) from here. Sounds interesting!! Yeah it should.
So hope this would be pretty helpful to you guys. Most of you might be thinking at this statement
IAsyncResult iftAr = b.BeginInvoke(5, 5, null, null);
This time I’ve passed null for IAsyncCallback and Object parameters. Going forward for the details, In the Part II will discuss about the uses and importance of these parameters.

Thanks & Cheers…

1 comment: