0% found this document useful (0 votes)
44 views

Explaining Delegates in C

1. Delegates in C# allow functions to be passed as arguments to other functions like callbacks. They provide a way to call different functions dynamically based on requirements without changing calling code. 2. Events use delegates to implement the observer pattern where subscribers can subscribe to events raised by publishers. Events must use delegates with two arguments - the object raising the event and an event args object derived from EventArgs. 3. Multicast delegates allow multiple functions to be assigned to the same delegate so that calling the delegate will call all assigned functions. This is used to allow both logging to a file and displaying to console from the same delegate.

Uploaded by

Ramiro
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
44 views

Explaining Delegates in C

1. Delegates in C# allow functions to be passed as arguments to other functions like callbacks. They provide a way to call different functions dynamically based on requirements without changing calling code. 2. Events use delegates to implement the observer pattern where subscribers can subscribe to events raised by publishers. Events must use delegates with two arguments - the object raising the event and an event args object derived from EventArgs. 3. Multicast delegates allow multiple functions to be assigned to the same delegate so that calling the delegate will call all assigned functions. This is used to allow both logging to a file and displaying to console from the same delegate.

Uploaded by

Ramiro
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 18

1 Explaining Delegates in C# - Part 1 (Callback and Multicast delegates) 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); // 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 as you can see in the output below!!!

//#1

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

public class Example2 {

2 // 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) {

3 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 {

4 // 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) {

5 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. I hope this clarifies the basic concepts of delegates, and why you would use them in the first place. In the next post, I will tell you about how to use delegates for events. Stay tuned! Explaining Delegates in C# - Part 2 (Events) In my previous post, I spoke about a few very basic and simple reasons of using delegates - primarily callback. In this post, I'll talk about 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;

6 } 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() {

7 InventoryManager inventoryManager = new InventoryManager(); Console.WriteLine("Main > Instantiating the subscriber... \n\n"); InventoryLogger inventoryLog = new InventoryLogger(inventoryManager); inventoryManager.LogEvent("Rahul Soni"); Console.ReadLine(); } }; } In the next post, I will talk about Asynchronous callbacks using delegates. Explaining Delegates in C# - Part 3 (Events again) I was thinking that the previous post on Events and Delegates was quite self-explanatory. A couple of days ago, I received an email which said that may be it is not that good as to explain what I want to achieve in life through that event. So, I thought why not write another post on Delegates and Events to make things clearer from a practical (or may be not so practical, "BUT" just another simple and practical example. There is no harm in trying, after all...) 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! In this case, 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);

8 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");

9 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(); } } } Hope this helps, Explaining Delegates in C# - Part 4 (Asynchronous Callback - Way 1) So far, I have discussed about Callback, Multicast delegates, Events using delegates, and yet another example of events using delegates. In this post, I will take this topic one step forward and show you an example of 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... 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 , 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 Before we proceed, let's see what happened to the delegate from my previous post (in ILDASM)...

10

Take a look in the figure above and notice that just creating the delegate in your code, actually created a class behind the scenes with a few methods called Invoke, BeginInvoke, and EndInvoke!!!!! Pretty smart, right? <Snippet from MSDN> Delegates enable you to call a synchronous method in an asynchronous manner. When you call a delegate synchronously, the Invoke method calls the target method directly on the current thread. If the BeginInvoke method is called, the common language runtime (CLR) queues the request and returns immediately to the caller. The target method is called asynchronously on a thread from the thread pool. The original thread, which submitted the request, is free to continue executing in parallel with the target method. If a callback method has been specified in the call to the BeginInvoke method, the callback method is called when the target method ends. In the callback method, the EndInvoke method obtains the return value and any input/output or output-only parameters. If no callback method is specified when calling BeginInvoke, EndInvoke can be called from the thread that called BeginInvoke. </Snippet from MSDN> 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. In this post we will take a look at the first way!! 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 . 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;

11 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); } } } I will discuss about more ways of doing asynchronous programming in some of my next posts. Stay tuned!!! Explaining Delegates in C# - Part 5 (Asynchronous Callback - Way 2) In this part of making asynchronous programming with delegates, we will talk about a different way, which is a little better than Way 1. In the previous post, it was like a husband telling his wife... 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

12 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 (in fact, it is always better to run it!!!) 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;

13 //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); } }

14 } I will discuss about more ways of doing asynchronous programming in some of my next posts. Stay tuned!!! Explaining Delegates in C# - Part 6 (Asynchronous Callback - Way 3) By now, I have shown the following usages of delegates... Callback and Multicast delegates Events One more Event Asynchronous Callback - Way 1 - BeginInvoke > EndInvoke Asynchronous Callback - Way 2 - BeginInvoke > AsyncWaitHandle.WaitOne(x) > EndInvoke > AsynWaitHandle.Close() In this part, we will take a look at how you could use polling to figure out if the Asynchronous call is complete. So, to carry on with the Husband-Wife analogy from my previous posts, 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

15 // 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); } } } I will discuss about more ways of doing asynchronous programming in some of my next posts. Stay tuned!!! Explaining Delegates in C# - Part 7 (Asynchronous Callback - Way 4) This is the final part of the series that started with... Callback and Multicast delegates Events One more Event Asynchronous Callback - Way 1 - BeginInvoke > EndInvoke Asynchronous Callback - Way 2 - BeginInvoke > AsyncWaitHandle.WaitOne(x) > EndInvoke > AsynWaitHandle.Close() Asynchronous Callback - Way 3 - BeginInvoke > Poll for result's IsCompleted > EndInvoke 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

16 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 {

17 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

18 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); } } } There will be more on Delegates and Asynchronous programming going forward. Stay tuned!

You might also like