Explaining Delegates in C#

The Basics

I hear a lot of confusion around Delegates in C#, and today I am going to give it shot of explaining the stuff with easy to understand examples. First things first… I will consciously try NOT to use any technical jargon to explain this concept.

So here we go, without giving any technical explanation, let’s start with a console application...

// Declaration
public delegate void VerySimpleDelegate();
class TestDelegate
{
   public static void Foo()
   {
      Console.WriteLine("Foo was called by a delegate!");
   }
   public static void Bar()
   {
      Console.WriteLine("Bar was called by a delegate!");
   }
   public static void Main()
   {
     // Instantiation
     VerySimpleDelegate vsd = new VerySimpleDelegate(Foo);  //#1
     // Invocation
     vsd();
     //Another Instantiation
     vsd = Bar;
     vsd();
   }
}

What do you notice when you run the application? Yes, you are right… you are not calling the functions Foo and Bar directly! Instead, you are creating a delegate (#1). Also notice, you just assigned vsd = Bar, which was another function name. Calling vsd again in the next line called the function called Bar!

Thus, to use delegate you need to use the following approach… (check the comments in code snipped above to find the following)

  • Declaration
  • Instantiation
  • Invocation

May be you are thinking, why all this headache? What is the need to do all this?

Reason 1 > You have to call different functions based on some dynamic requirement but you don’t want your function calls to change. In our previous code snipped vsd() doesn’t change, although the actual function that it is referring to has changed altogether.

Let’s take a look at another sample, Example 2.

public class Example2
{
   // Declaration - Take 1 parameter, return nothing
   public delegate void LogHandler(string message);
   // Instantiation - Create a function which takes delegate as one parameter
   // Verify if it is null before you use it
   public void Process(LogHandler logHandler)
   {
      if (logHandler != null)
      {
         logHandler("Process() begin");
      }
      if (logHandler != null)
      {
         logHandler("Process() end");
      }
   }
}

public class Example2DelegateConsumer
{
    // Create a method with the same signature as the delegate
    static void Logger(string s)
    {
       Console.WriteLine(s);
    }
    public static void Main(string[] args)
    {
       Example2 ex2 = new Example2();
       // Invocation in the client
       Example2.LogHandler myLogger = new Example2.LogHandler(Logger);
       ex2.Process(myLogger);
    }
}

Reason 2> As you can see above, you can use delegates to call static functions. In our case, the function Logger with a parameter was being called by Process function in the Example2 class. This approach is called Callback.

public class Example3
{
   // Declaration - Take 1 parameter, return nothing
   public delegate void LogHandler(string message);
   // Instantiation - Create a function which takes delegate as one parameter
   // Verify if it is null before you use it
   public void Process(LogHandler logHandler)
   {
       if (logHandler != null)
       {
           logHandler("Process() begin");
       }
       if (logHandler != null)
       {
           logHandler("Process() end");
       }
    }
}
public class FileLogger
{
  FileStream fs;
  StreamWriter sw;
  // Constructor
  public FileLogger(string filename)
  {
    fs = new FileStream(filename, FileMode.Create);
    sw = new StreamWriter(fs);
  }
  // Create a method with the same signature as the delegate
  public void Logger(string s)
  {
    sw.WriteLine(s);
  }
  public void Close()
  {
    sw.Close();
    fs.Close();
  }
}
public class Example3DelegateConsumer
{
  static void Main(string[] args)
  {
    FileLogger fl = new FileLogger("C:\\Labfiles\\process.log");
    Example3 ex3 = new Example3();
    // Invocation in the client
    // Notice that now instead of Logger function, we are passing fl.Logger function.
    Example3.LogHandler myLogger = new Example3.LogHandler(fl.Logger);
    ex3.Process(myLogger);
    fl.Close();
  }
}

Reason 3> Without changing the Example3 delegate, we were able to change the location where a log needs to be written. In the previous example, you would have noticed that we had a Logger function in the same class. Now, we know we can point to any function with the same signature as a delegate from a different class as well. In our example we called the function from the class FileLogger. The key here is that the Logger function is not in the Example3DelegateConsumer class!!!

What if you want to display the results, and write them at the same time??

namespace Delegates4
{
  public class Example4
  {
    // Declaration - Take 1 parameter, return nothing
    public delegate void LogHandler(string message);
    // Instantiation - Create a function which takes delegate as one parameter
    // Verify if it is null before you use it
    public void Process(LogHandler logHandler)
    {
      if (logHandler != null)
      {
        logHandler("Process() begin");
      }
      if (logHandler != null)
      {
        logHandler("Process() end");
      }
    }
  }
  public class FileLogger
  {
    FileStream fs;
    StreamWriter sw;
    // Constructor
    public FileLogger(string filename)
    {
       fs = new FileStream(filename, FileMode.Create);
       sw = new StreamWriter(fs);
    }
    // Create a method with the same signature as the delegate
    public void Logger(string s)
    {
      sw.WriteLine(s);
    }
    public void Close()
    {
      sw.Close();
      fs.Close();
    }
  }
  public class Example4DelegateConsumer
  {
     // Create a method with the same signature as the delegate
     static void Logger(string s)
     {
        Console.WriteLine(s);
     }
     static void Main(string[] args)
     {
        FileLogger fl = new FileLogger("C:\\Labfiles\\process.log");
        Example4 ex4 = new Example4();
        // Invocation in the client
        // Notice that now instead of Logger function, we are passing fl.Logger function
        // along with another Logger which is defined in the same class
        Example4.LogHandler myLogger = null;
        myLogger += new Example4.LogHandler(Logger);
        myLogger += new Example4.LogHandler(fl.Logger);
        ex4.Process(myLogger);
        fl.Close();
      }
   }
}

Reason 4> As you can see above, we have registered two methods for the same delegate. This is what is typically mentioned as Multicast delegate. By default in C#, delegates are multicast.

Creating Events using delegates

I have tried to annotate the class with comments so that it is easy to comprehend. But IMHO, the best way to figure out what’s going on is to copy/paste the following code and create breakpoint in the Main() class and hit F11 to step into the code. You will notice that Events are based on the Observer or Publish/Subscribe design pattern.

There are 3 rules which are MANDATORY when you are planning to create events…

Rule 1> The delegate must be defined with two arguments

Rule 2> These arguments always represent TWO objects… The Publisher (object that raised the event) Event Information object

Rule 3> The 2nd object HAS to be derived from EventArgs

Step through the code and see for yourself how easy this is!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DelegatesAndEvents {
 //There are 3 rules which are MANDATORY when you are planning to create events
 //Rule 1> The delegate must be defined with two arguments
 //Rule 2> These arguments always represent TWO objects… 
 //          The Publisher (object that raised the event)
 //          Event Information object
 //Rule 3> The 2nd object HAS to be derived from EventArgs
 //To comply with Rule 3 we create a LoggerEventArgs which is derived from EventArgs
 class LoggerEventArgs: EventArgs {
  //constructor to initialize the UserName property
  public LoggerEventArgs(string UserName) {
   this.username = UserName;
  }
  string username;
  public string UserName {
   get {
    return username;
   }
  }
 };
 //To comply with Rule 1 and 2, in the publisher class we created a delegate and event declaration
 class InventoryManager // Publisher
 {
  public delegate void LoggerEventHandler(object source, LoggerEventArgs e);
  public event LoggerEventHandler OnInventoryUpdate;
  //This method will fire an event called OnInventoryUpdate whenever called
  public void LogEvent(string username) {
   LoggerEventArgs e = new LoggerEventArgs(username);
   if (OnInventoryUpdate != null) {
    Console.WriteLine("LogEvent > Raising events to all subscribers...\n");
    OnInventoryUpdate(this, e);
   }
  }
 };
 class InventoryLogger // Subscriber
 {
  InventoryManager inventoryManager;
  public InventoryLogger(InventoryManager inventoryManager) {
   Console.WriteLine("InventoryWatcher > Subscribing to OnInventoryUpdate event...\n");
   this.inventoryManager = inventoryManager;
   //Wiring the event so that the event is fired
   inventoryManager.OnInventoryUpdate +=
    new InventoryManager.LoggerEventHandler(OnInventoryUpdate);
  }
  void OnInventoryUpdate(object source, LoggerEventArgs e) {
   Console.WriteLine("The guy who changed this inventory was... " + e.UserName);
  }
 }
 class DelegateEvents {
  public static void Main() {
   InventoryManager inventoryManager = new InventoryManager();
   Console.WriteLine("Main > Instantiating the subscriber... \n\n");
   InventoryLogger inventoryLog = new InventoryLogger(inventoryManager);
   inventoryManager.LogEvent(" rahul soni Soni");
   Console.ReadLine();
  }
 };
}

Asynchronous callbacks using delegates

Here is the requirement…

You have a class with a special number. You have another (or may be a lot of other) class which is using it. The requirement now is that, whenever the special number is changed, all the other guys should be notified about what this number was and what it has become!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace EventAndDelegateDemo {
 //There are 3 rules which are MANDATORY when you are planning to create events
 //Rule 1> The delegate must be defined with two arguments
 //Rule 2> These arguments always represent TWO objects… 
 //          The Publisher (object that raised the event)
 //          Event Information object
 //Rule 3> The 2nd object HAS to be derived from EventArgs
 //Step 1> Create a class that inherits from EventArgs (To comply with Rule 3)
 class NotifyChangeEventArgs: EventArgs {
  //constructor to initialize the SpecialNumberOld and SpecialNumberNew property
  public NotifyChangeEventArgs(int SpecialNumberOld, int SpecialNumberNew) {
   this.SpecialNumberNew = SpecialNumberNew;
   this.SpecialNumberOld = SpecialNumberOld;
  }
  public int SpecialNumberNew {
   get;
   set;
  }
  public int SpecialNumberOld {
   get;
   set;
  }
 };
 class SpecialNumberClass // Publisher
 {
  //Publisher declares a special delegate and event (Rule 1 & 2)
  public delegate void SpecialNumberHandler(object source, NotifyChangeEventArgs e);
  public event SpecialNumberHandler OnSpecialNumberUpdate;
  //Constructor to set the value of the _specialNumber directly.
  //Notice that we are not setting the SpecialNumber property!
  public SpecialNumberClass(int number) {
    _specialNumber = number;
   }
   //This property will fire an event called OnSpecialNumberUpdate whenever called
   //The effect is that is the special number is changed from outside, all the guys
   //would be notified. It is upto them to listen to it or not listen to it.
  private int _specialNumber;
  public int SpecialNumber {
   get {
    return _specialNumber;
   }
   //Put a breakpoint here on set and step into (F11)
   set {
    if (OnSpecialNumberUpdate != null) {
     //This is the guy who would fire that notify event called OnSpecialNumberUpdate
     //Basically, before you fire that event, you can set up that value for EventArgs
     //In my case, I have set the value of e's old value and new value...
     //to the _specialNumber and value (which would be the new value)
     //Notice that we are just firing the events with appropriate EventArgs...
     //We haven't thrown any output as such yet!!!!
     NotifyChangeEventArgs e = new NotifyChangeEventArgs(_specialNumber, value);
     Console.WriteLine("Raising Events to all the subscribers...\n");
     OnSpecialNumberUpdate(this, e);
    }
   }
  }
 };
 class SpecialNumberUpdateListener // Subscriber
 {
  //Here we are just creating a new Object called objSN for my SpecialNumberClass
  SpecialNumberClass objSN;
  //In this method, I would go ahead and bind my event
  public SpecialNumberUpdateListener(SpecialNumberClass spClass) {
    Console.WriteLine("SpecialNumber listener > Subscribing to the event...\n");
    this.objSN = spClass;
    //Wiring the event so that the event is fired
    spClass.OnSpecialNumberUpdate += new SpecialNumberClass.SpecialNumberHandler(OnSpecialNumberUpdate);
   }
   //This is the event that would be invoked whenever you change the value
   //Try putting a break point here and see how it gets hit when the number changes
   //Also notice how we use the Event args to grab the details and finally print it out
  void OnSpecialNumberUpdate(object source, NotifyChangeEventArgs e) {
   Console.WriteLine("The number has changed from {0} to {1} ", e.SpecialNumberOld, e.SpecialNumberNew);
  }
 }
 class EventDemo {
  public static void Main() {
   //Creating a new Special Number (Publisher) class with initial value of 20
   SpecialNumberClass snc = new SpecialNumberClass(30);
   //Creating a Subscriber/listener class
   SpecialNumberUpdateListener s = new SpecialNumberUpdateListener(snc);
   //Changing the value so that the event is triggered.
   //Put a breakpoint and step into the code (F11)
   snc.SpecialNumber = 40;
   Console.ReadLine();
  }
 }
}

Asynchronous callbacks using delegates

Let’s say a client calls a method that takes 40 minutes to complete, how do we communicate with the client?

Option 1> Keep showing that busy Cursor for 40 minutes!!!

Option 2> Keep updating the client with appropriate messages, like… “oh yes… we might take another light year to complete, please wait… oh common….. show some patience… do yoga… etc...”

Option 3> Tell the client that “okay Sir… you are done with your part… go away take a vacation. Whenever I’m done I will let you know”

As you may agree... Option 3 is a more effective way. Not because it gives you enough time to complete your job Winking, but because the client is not just waiting on you. He is gone after giving you the job and may be doing something else in life. HE is FREE… and FREEDOM is good :-)

Asynchronous calls has two important parts... BeginInvoke and EndInvoke. Once BeginInvoke is called, EndInvoke can be called anytime. The catch is that EndInvoke is a blocking call. Thus, it would block the calling thread until it is complete. There are several ways in which you could work with BeginInvoke and EndInvoke at tandem.

The following code is almost like a husband telling his wife (whom he is dropping in a mall for some shopping!!)…

You know honey, I have a lot of work to do. Why don’t you help me up by doing something that you can do pretty well Winking. In the meantime, I will take care of some other stuff. As soon as I am done, I promise I will pick you up.

The good thing with this approach is that the wife (in our case, a new thread) is doing something, while the main thread can continue to do something else. The catch is that, the husband (main thread) must be aware that once its job is done, it will have to wait (blocking call) for his wife (the other thread)!! Just like I do, well... almost always ;-)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace EventAndDelegateDemo {
 //The delegate must have the same signature as the method. In this case,
 //we will make it same as TortoiseMethod
 public delegate string TortoiseCaller(int seconds, out int threadId);
 public class TortoiseClass {
  // The method to be executed asynchronously.
  public string TortoiseMethod(int seconds, out int threadId) {
   Console.WriteLine("The slow method... executes...on thread {0}", Thread.CurrentThread.ManagedThreadId);
   for (int i = 0; i < 5; i++) {
    Thread.Sleep(seconds / 5 * 1000);
    Console.WriteLine("The async task is going on... {0}", Thread.CurrentThread.ManagedThreadId);
   }
   threadId = Thread.CurrentThread.ManagedThreadId;
   return String.Format("I worked in my sleep for {0} seconds", seconds.ToString());
  }
 }
 //Now, that we are done with the declaration part, let's proceed to
 //consume the classes and see it in action
 //The algorithm would be very simple...
 //         1. Call delegate's BeginInvoke
 //         2. Do some work on the main thread
 //         3. Call the delegate's EndInvoke
 public class TortoiseConsumer {
  public static void Main() {
   //Instantiate a new TortoiseClass
   TortoiseClass tc = new TortoiseClass();
   //Let's create the delegate now
   TortoiseCaller caller = new TortoiseCaller(tc.TortoiseMethod);
   //The asynchronous method puts the thread id here
   int threadId;
   //Make the async call. Notice that this thread continues to run after making this call
   Console.WriteLine("Before making the Async call... Thread ID = {0}", Thread.CurrentThread.ManagedThreadId);
   IAsyncResult result = caller.BeginInvoke(30, out threadId, null, null);
   Console.WriteLine("After making the Async call... Thread ID = {0}", Thread.CurrentThread.ManagedThreadId);
   Console.WriteLine("Perform more work as the other thread works...");
   for (int i = 30; i > 1; i--) {
    Thread.Sleep(1000);
    Console.WriteLine("{0}...", i);
   }
   Console.WriteLine("Waiting for the async call to return now...");
   //Notice that this call will be a blocking call
   string returnValue = caller.EndInvoke(out threadId, result);
   Console.WriteLine("The call got executed on thread {0}", threadId);
   Console.WriteLine("The value returned was - {0}", returnValue);
  }
 }
}

Another way

Earlier, the conversation was, “You know honey, I have a lot of work to do. Why don’t you help me up by doing something that you can do pretty well ;-) In the meantime, I will take care of some other stuff. As soon as I am done, I promise I will pick you up.”

Notice that, although it looks like a good way of getting the work done, it has a tiny flaw (not really a flaw, but I will still call it a flaw to make my story!). What if their 6 year old kid called the husband in the meantime? Would the husband appreciate it while waiting he can do nothing else but wait? I mean, what if he has to just pick up the phone and tell his child, you know kiddo, I am here at the mall waiting for your mom. I’ll be back in some time! This example just does that. Basically, we know that EndInvoke is a blocking call. If you make this call, you can do nothing but wait, which may make your UI look awful. In this example, we will be waiting for the async call to complete, but still be free enough to do some stuff.

Okay, enough said... let’s take a look at the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace EventAndDelegateDemo {
 //The delegate must have the same signature as the method. In this case,
 //we will make it same as TortoiseMethod
 public delegate string TortoiseCaller(int seconds, out int threadId);
 public class TortoiseClass {
  // The method to be executed asynchronously.
  public string TortoiseMethod(int seconds, out int threadId) {
   Console.WriteLine("The slow method... executes...on thread {0}", Thread.CurrentThread.ManagedThreadId);
   for (int i = 0; i < 5; i++) {
    Thread.Sleep(seconds / 5 * 1000);
    Console.WriteLine("The async task is going on thread # {0}", Thread.CurrentThread.ManagedThreadId);
   }
   threadId = Thread.CurrentThread.ManagedThreadId;
   return String.Format("I worked in my sleep for {0} seconds", seconds.ToString());
  }
 }
 //Now, that we are done with the declaration part, let's proceed to
 //consume the classes and see it in action
 //The algorithm would be very simple...
 //         1. Call delegate's BeginInvoke
 //         2. Do some work on the main thread
 //         3. Call the result's AsyncWaitHandle.WaitOne() which would be a blocking call
 //         4. Call EndInvoke which won't be a blocking call this time!
 //         5. Close the result's AsyncWaitHandle, explicitly.
 public class TortoiseConsumer {
  static void Main() {
   //Instantiate a new TortoiseClass
   TortoiseClass tc = new TortoiseClass();
   //Let's create the delegate now
   TortoiseCaller caller = new TortoiseCaller(tc.TortoiseMethod);
   //The asynchronous method puts the thread id here
   int threadId;
   //Make the async call. Notice that this thread continues to run after making this call
   Console.WriteLine("Before making the Async call... Thread ID = {0}", Thread.CurrentThread.ManagedThreadId);
   IAsyncResult result = caller.BeginInvoke(30, out threadId, null, null);
   //After calling the method asynchronously, the main thread continues to work...
   Console.WriteLine("After making the Async call... Thread ID = {0}", Thread.CurrentThread.ManagedThreadId);
   Console.WriteLine("Perform more work as the other thread works...");
   for (int i = 10; i > 1; i--) {
    Thread.Sleep(1000);
    Console.WriteLine("{0}...", i);
   }
   Stopwatch s = new Stopwatch();
   //Calling WaitOne is a blocking call. As soon as you call WaitOne, you won't proceed further
   //in this main thread until the Async call completes
   Console.WriteLine("Before calling WaitOne... {0} milliseconds", s.ElapsedMilliseconds.ToString());
   s.Start();
   //The next call can be a blocking call (in our case it WILL be a blocking call since the Tortoise
   //method takes 30 seconds to complete. By now, already 12 seconds are over!
   result.AsyncWaitHandle.WaitOne(2000);
   //The good thing is that, now you can do update the client while still waiting for the call to complete
   Console.WriteLine("Well, I know waiting could be boring, but at the moment I am still waiting...");
   //Waiting for 5 seconds now!
   result.AsyncWaitHandle.WaitOne(5000);
   //Updating once again...
   Console.WriteLine("Argghh... when will this end??");
   //Waiting till the async call is complete (Notice that this can be blocking!!)
   result.AsyncWaitHandle.WaitOne();
   s.Stop();
   Console.WriteLine("After calling WaitOne... {0} milliseconds", s.ElapsedMilliseconds.ToString());
   //Notice that this call will NOT be a blocking call as it was in our previous example!
   string returnValue = caller.EndInvoke(out threadId, result);
   //Close the wait handle. This is important, since it is not automatically cleared.
   //Only the next GC can collect this native handle. So, it is a good practise to clear
   //this handle as soon as you are done with it.
   result.AsyncWaitHandle.Close();
   Console.WriteLine("The call got executed on thread {0}", threadId);
   Console.WriteLine("The value returned was - {0}", returnValue);
  }
 }
}

Use polling to figure out if the Asynchronous call is complete

The present scenario would be something like… the husband leaves his wife at the mall for shopping, comes back and is waiting in the parking lot for his wife. He knows that their kid is waiting at their home, so he keeps calling his wife every N minutes, and then calls his kid to say... sorry kiddo, another few moments!!! In the code that follows the person looking at the UI is the kiddo, main thread = husband, and the thread doing asynchronous stuff is wife.

Let’s take a look at the code (and read the comments to get a better understanding!).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
namespace EventAndDelegateDemo {
 //The delegate must have the same signature as the method. In this case,
 //we will make it same as TortoiseMethod
 public delegate string TortoiseCaller(int seconds, out int threadId);
 public class TortoiseClass {
  // The method to be executed asynchronously.
  public string TortoiseMethod(int seconds, out int threadId) {
   Console.WriteLine("The slow method... executes...on thread {0}", Thread.CurrentThread.ManagedThreadId);
   for (int i = 0; i < 5; i++) {
    Thread.Sleep(seconds / 5 * 1000);
    //Console.WriteLine("The async task is going on thread # {0}", Thread.CurrentThread.ManagedThreadId);
   }
   threadId = Thread.CurrentThread.ManagedThreadId;
   return String.Format("I worked in my sleep for {0} seconds", seconds.ToString());
  }
 }
 //Now, that we are done with the declaration part, let's proceed to
 //consume the classes and see it in action
 //The algorithm would be very simple...
 //         1. Call delegate's BeginInvoke
 //         2. Do some work on the main thread
 //         3. Keep polling using result's IsCompleted property
 //         4. Call EndInvoke which won't be a blocking call this time!
 public class TortoiseConsumer {
  static void Main() {
   //Instantiate a new TortoiseClass
   TortoiseClass tc = new TortoiseClass();
   //Let's create the delegate now
   TortoiseCaller caller = new TortoiseCaller(tc.TortoiseMethod);
   //The asynchronous method puts the thread id here
   int threadId;
   //Make the async call. Notice that this thread continues to run after making this call
   Console.WriteLine("Before making the Async call... Thread ID = {0}", Thread.CurrentThread.ManagedThreadId);
   IAsyncResult result = caller.BeginInvoke(25, out threadId, null, null);
   //After calling the method asynchronously, the main thread continues to work...
   Console.WriteLine("After making the Async call... Thread ID = {0}", Thread.CurrentThread.ManagedThreadId);
   Console.WriteLine("Start the polling...\nWaiting for the Tortoise method to complete...");
   //The IAsynResult interface uses IsCompleted property which you can use to figure out if the call is complete
   //Notice that this will be a  blocking call until the Async call completes.
   while (result.IsCompleted == false) {
    Console.Write(".");
    Thread.Sleep(500);
   }
   //Normally, EndInvoke would be a blocking call, but in this case... it won't be.
   //The reason is that we now know that the Async call is completed!
   string returnValue = caller.EndInvoke(out threadId, result);
   Console.WriteLine("\nThe call got executed on thread {0}", threadId);
   Console.WriteLine("The value returned was - {0}", returnValue);
  }
 }
}

The Other Guy!

In this scenario, if we go with the husband-wife-kiddo analogy, I will need to introduce another guy! No no no… please don’t misunderstand me... he is the just the driver ;-)

So, now the husband drops his wife at the mall. And instead of coming back to pick her up, he simply says… honey, I am going to the office. When you are done shopping, call the driver (+91-97415-xxxxx) and he would take you home.

NOTE>

  • As soon as the main thread calls the asynchronous method, his part is done
  • The callback method is executed on the ThreadPool thread
  • The call to BeginInvoke (so far… it has been something like… caller.BeginInvoke(25, out threadId, null, null);) would now have the 3rd parameter as an AsyncCallback which contains the callback method name.
  • The 4th parameter takes an object which your callback method might like to use.
  • The ThreadPool threads are background threads. This means that they won’t keep the application running in case the main thread ends. Thus, the main thread has to be alive for long enough to ensure that the background threads are done processing.

Let’s take a look at the code (and read the comments to get a better understanding!).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Runtime.Remoting.Messaging;
namespace EventAndDelegateDemo {
 //The delegate must have the same signature as the method. In this case,
 //we will make it same as TortoiseMethod
 public delegate string TortoiseCaller(int seconds, out int threadID);
 public class TortoiseClass {
  // The method to be executed asynchronously.
  public string TortoiseMethod(int seconds, out int threadID) {
   threadID = Thread.CurrentThread.ManagedThreadId;
   Console.WriteLine("The slow method... executes...on thread {0}", Thread.CurrentThread.ManagedThreadId);
   for (int i = 0; i < 5; i++) {
    Thread.Sleep(seconds / 5 * 1000);
    Console.WriteLine("The async task is going on thread # {0}", Thread.CurrentThread.ManagedThreadId);
   }
   return String.Format("I worked in my sleep for {0} seconds", seconds.ToString());
  }
 }
 //Now, that we are done with the declaration part, let's proceed to
 //consume the classes and see it in action
 //The algorithm would be very simple...
 //         1. Call delegate's BeginInvoke and pass the callback method's name
 //         2. Do some work on the main thread
 //         3. Your callback method is called when the processing completes. 
 //         4. Retrieve the delegate and call EndInvoke which won't be a blocking call this time!
 public class TortoiseConsumer {
  static void Main() {
    //Instantiate a new TortoiseClass
    TortoiseClass tc = new TortoiseClass();
    //Let's create the delegate now
    TortoiseCaller caller = new TortoiseCaller(tc.TortoiseMethod);
    //This is a dummy variable since this thread is not supposed to handle the callback anyways!!!
    int dummy = 0;
    //Start the asynchronous call with the following parameters...
    //Parameter 1 = 30 > In my example the tortoise class will now do something for 30 seconds
    //Parameter 2 = Dummy variable, just to get the output of the threadID
    //Parameter 3 = new AsyncCallback(CallbackMethod) > This is the method which would be called once the async task is over
    //Parameter 4 = Object > This is a string which would display the information about the async call
    IAsyncResult result = caller.BeginInvoke(30, out dummy, new AsyncCallback(CallThisMethodWhenDone),
     "The call executed on thread {0}, with return value \"{1}\".");
    Console.WriteLine("The main thread {0} continues to execute...", Thread.CurrentThread.ManagedThreadId);
    //The callback method will use a thread from the ThreadPool.
    //But the threadpool threads are background threads. This implies that if you comment the line below
    //you would notice that the callback method is never called, since the background threads can't stop the main
    //program to terminate!
    Thread.Sleep(3000);
    Console.WriteLine("The main thread ends. Change the value 3000 in code to 40000 and see the result");
   }
   //The signature for the call back method must be same System.IAsyncResult delegate.
  static void CallThisMethodWhenDone(System.IAsyncResult ar) {
   //To retrieve the delegate we will cast the IAsyncResult to AsyncResult and get the caller
   AsyncResult result = (AsyncResult) ar;
   TortoiseCaller caller = (TortoiseCaller) result.AsyncDelegate;
   //Get the object (in our case it is just a format string) that was passed while calling BeginInvoke!  
   string formattedString = (string) ar.AsyncState;
   // Define a variable to receive the value of the out parameter.
   // If the parameter were ref rather than out then it would have to
   // be a class-level field so it could also be passed to BeginInvoke.
   //The following variable would take the Thread ID 
   int threadId = 0;
   //At this point, the threadID won't be a dummy variable as it was in the Main method
   //We are passing threadID to get the output from the TortoiseMethod
   string returnValue = caller.EndInvoke(out threadId, ar);
   //Use the format string to format the output message.
   Console.WriteLine(formattedString, threadId, returnValue);
  }
 }
}

I hope this post helps you understand and remember the concept of delegates a little better.

rahul soni

© 2023, Attosol Private Ltd. All Rights Reserved.