Delegates And Async Programming

Recall that the .Net delegate type is essentially a type-safe, object oriented, function pointer. Let's get into a 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 the ability to maintain a list of method addresses, all of which may be invoked at a time or a later time.

Let us 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 matches its signature. After compiling your program, the defining assembly will have a 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 a delegate object in a Synchronous manner. The Invoke() method does not need to be directly called in code, but 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 another 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 defines 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 be based on the format of the C# delegate. The final two arguments will always be System.AsynCallBack and System.Object. In the following example I'll pass null for both. Also note that the return value of EndInvoke() is an integer, based on the return type of BinaryOp delegate, while a parameter of this method is of type IAsyncResult which is a return type of BeginInvoke().

The System.IAsyncResult Interface

The IAsyncResult compatible object returned from 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:

Delagate

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

Synchronizing the calling thread

If you think carefully then you'll realize that the span of time calling BeginInvoke() and EndInvoke() is clearly less than five seconds. Therefore once the "Doing more work in Main()!" prints to the console, the calling thread is blocked and waiting for the secondary thread to complete before being able to obtain the result of the Add() method. Therefore this is an effective yet another Synchronous call. Agree?

Obviously the meaning of being asynchronous is lost 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();
}

Delagate

You see the more work is done in Main() now. Its fully utilized and an asynchronous way of doing it.

The portion we have added to the Main() can also we written in an 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 I hope this would be pretty helpful to you guys. Most of you might be thinking of this statement:

IAsyncResult iftAr = b.BeginInvoke(5, 5, null, null);

This time I've passed null for IAsyncCallback and Object parameters. Going forward for details, Part II will discuss the uses and importance of these parameters.

Thanks & Cheers…


Similar Articles