Saturday, April 28, 2012

.NET Delegates explained

.NET delegate type is a type-safe, object-oriented, function pointer. A delegate dynamically wires up a a method caller to its target method. There are two aspects to a delegate: type and instance. A delegate type defines a protocol to which the caller and target will conform, comprising a list of parameter types and a return type. A delegate instance is an object that refers to one (or more) target methods conforming to that protocol.

A delegate instance literally acts as a delegate for the caller: the caller invokes the delegate, and then the delegate calls the target method. This indirection decouples the caller from the target method.

When you declare a .NET delegate, the C# compiler responds by building a sealed class that derives from System.MulticastDelegate which provide every delegate with the ability to maintain a list of method addresses, all of which may be invoked at a later time. A delegate type is declared/defined as:
public delegate int SpecialCalc( int x, int y);
Based on its definition SpecialCalc can point to any method taking two integers as arguments and returning an integer. Once compiled the defining assembly contains a class definition that is dynamically generated when you build your project, based on the delegate declaration. In above case this class looks like:
public sealed class SpecialCalc : System.MulticastDelegate
{
           public SpecialCalc ( 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 );
}
Invoke method: this method is used to invoke the methods maintained by a delegate object in synchronous manner. Therefore the calling thread i.e. primary thread of the application, is forced to wait until the delegate invocation completes. Also note whenever you call any method in C#, it's called by Invoke method bydefault so you don't need to deliberately use Invoke method. For example, consider the following delegate instance declaration
Class Test
{
       static void Main()
       {
           //Create delegate instance, could be write as = new SpecialCalc(SpecialSum);           SpecialCalc sc = SpecialSum;
           // Invoke Special Sum() in a synchronous manner. Could be write as sc.Invoke(3,4);           int result = sc(3,4);
            Console.WriteLine (result);           //  10       }
       static int SpecialSum ( int x, int y)
       {
           // Pause to simulate a lengthy operation           Thread.Sleep(5000);
            return 2x + y;
       }}
 Within the SpecialSum() method, we are invoking Thread.Sleep() method to suspend the calling thread for approximately five seconds to simulate a lengthy task. As you are invoking the SpecialSum() method in a synchronous manner, the Main() method will not print out the result of the operation until the SpecialSum() method has been completed.

Although we made SpecialSum method a lengthy operation deliberately but it might be possible that you actually create a task which takes that much amount of time. While performing such operation (on main thready or synchronously) the application will appear to hang for some amount of time. Until this lengthy operation is completely processed, all other aspects of application such as menu activation, toolbar, console output, mouse click are unresponsive. Thus you should tell a delegate to invoke a method on a separate thread of a execution in order to avoid blocking all other aspects of application. Fortunately every .NET delegate type is automatically equipped with this capability.

BeginInvoke() and EndInvoke() methods: As we discussed earlier, when you declare a delegate its delegate class contains following two methods whose syntax is as under:
public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state);
// used to invoke a method asynchronously.public int EndInvoke (IAsyncResult result);
// Used to fetch the return value of the invoked method.
 First stack of parameters passed into BeingInvoke() will be based on the format of the delegate, in our case SpecialCalc accepts 2 parameters that's why I mentioned two parameters. Last two parameters will always be System.AsyncCallback and System.Object.

IAsyncResult Interface: The BeginInvoke method always returns an object implementing the IAsyncResult inteface while EndInvoke() requires an IAsyncResult type as its sole parameter. The IAsyncResult object returned by the BeginInvoke() method is basically a coupling mechanism that allows the calling thread to object the result of the asynchronous method invocation at a later time via EndInvoke().
IAsyncResultInterface is defined as follows:
public intefact IAsyncResult
{
     object AsyncState {get;}
     WaitHandle AsyncWaitHandle { get; }
      bool CompletedSynchronously { get; }
      bool IsCompleted { get; }
}  // All of these members are explained below
In order to call SpecialCalc delegate asynchronously, you just need to modify the delegate invocation call from invoke to BeginInvoke so now your main method will look like
Class Test
{
     static void Main()
     {
     SpecialCalc sc = SpecialSum;
     // Invoke Special Sum() in an asynchronous manner.
     IAsyncResult iasyncR = sc.BeginInvoke(3,4, null, null);
     // Do other work on primary thread and obtain the result of SpecialSum() when ready
     int result = sc.EndInvoke( iasyncR );
     Console.WriteLine (result); // 10
     }
static int SpecialSum ( int x, int y)
{
     // Pause to simulate a lengthy operation
     Thread.Sleep(5000);
     return 2x + y;
}
}
When you run this code block you'll notice that there would be two threads one primary thread and other thread working on delegate operation. Your application won't be unresponsive this time, after invoking delegate you can carry on further operation and after performing them you can call EndInvoke() which will provide you the delegate method result.
Now suppose you did something after calling BeginInvoke method i.e
IAsyncResult iasyncR = sc.BeginInvoke (3,4, null, null);
DoThisHeavyOperation(); //Some other method int result = sc.EndInvoke ( iasyncR );
In above case if DoThisHeavyOperation method takes less than 5 seconds then again main thread will be blocked waiting for delegate to complete (i.e. EndInvoke call). Thus in order to overcome this big problem IAsyncResult interface provides the IsCompleted property. Using this member the calling thread is able to determine whether the asynchronous call has completed or not. If the method has not completed, IsCompleted returns false and the calling thread is free to carry on any other task. For example we can tweak above delegate invocation as:
IAsyncResult iasyncR = sc.BeginInvoke ( 3, 4, null, null);
DoThisHeavyOperation();  // Some other workwhile( !iasyncR.IsCompleted )    // Check whether delegate method is completed{
      // Do some other work till delegate is not completed}
// Delegate has finished its method, fetch result from EndInvokeint result = sc.EndInvoke( iasyncR );
In addition to the IsCompleted property, the IAsyncResult interface provides the AsyncWaitHandle property for more handling wait logic more smartly.
WaitHandle AsyncWaitHandle { get; }
AsyncWaitHandle returns an instance of WaitHandle type which exposes a method name WaitOne( ) using this method you can specify the maximum wait time. If the specified amount is exceeded, WaitOne( ) returns false. So you can replace the IsCompleted property implementation with WaitHandle as:
while ( iasyncR.AsyncWaitHandle.WaitOne ( 1000, true) )
{
           // Doing some other work} 
Although the above mentioned property does provide the way to synchronize your delegate call and main thread still its not a efficient approach.

Delegates with AsyncCallback: As we see above we keep on asking a delegate whether the asynchronously invoked method has completed or not, it would be more efficient to have the secondary thread inform the calling thread when the task is finished. To add such functionality to your delegate you need to pass an instance of the System.AsyncCallback delegate as a parameter to BeginInvoke( ) method. As soon as the asynchronous call will get completed, this AsyncCallback delegate will be invoked. Lets discuss about how these AsyncCallbacks are used with delegate.

As delegate type has specific syntax, AsyncCallback can only invoke methods having a specific pattern, which is a method having IAsyncResult as the sole parameter and returning nothing. i.e.
Notice the diffence between AsyncCallback & IAsyncResult spell.
void AsyncCallbackMethod ( IAsynchResult iasyncR)
Using AsyncCallback: Now create a new method with the syntax described above and which you want to execute after asynchronous method completes. Suppose in our example you we want to do another operation after SpecialSum operation is completed so create a new AsyncCallback to do that.
static void SpecialSumCompleted ( IAsyncResult iasyncR)
{
Console.WriteLine ("Special Sum operation completed and AsyncCallback is working");
// Invoking another special operation after SpecialSum is completedDoAnotherSpecialOperation( );
// Note that here I can't print the result of Special Sum method as that is not accessible here.}
The SpecialSumCompleted callback does not print the actual result of SpecialSum operation as the AsyncCallback delegate (SpecialSumCompleted) does not have access to the original SpecialCalc delegate created in the Main( ) scope and thus you can't call EndInvoke from within SpecialSumCompleted ( ). However there is a workaround for this problem which I'll discuss later.

This article is something which I used to read from couple of textbooks so noted down the excerpt here and will refer to it in future. Delegates and events are very interesting & useful topic in C# programming.

No comments:

Post a Comment