Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Delegates in C# - Attempt to Look Inside - Part 2

0.00/5 (No votes)
6 Oct 2010 1  
Call delegates asynchronously if you need

Introduction

This is the second part of the series of articles that I intend to write to explore this topic in depth. If you did not read the first one, I encourage you to do so. Read the article here.
In this part, I want to discuss different ways of invoking functions represented by delegates. The main part is actually when and how one should call a delegate asynchronously. Using asynchronous calls is considered advance technique, because it involves threading. I hope that after you are done with this article, you will be fully prepared to use those calls in your application. I want to use a different approach explaining delegates call. I will try to make up a life situation and apply it to the programming environment. I think this will make it easier to understand the issue and realize that there is no magic in using delegates; everything has its goal and purpose.

Life Problem

Tonight I am going to entertain some friends of mine. I have a list of things to do before they come. I want to be organized and this is the list I created.

  1. Clean up the room
  2. Make some food
  3. Set the table
  4. Get ready (take a bath, dress, etc...)

Well, I don't think these things are hard to do, but I am not a cook. So I need to use some professional help.
I can order some food by the phone. The restaurant I am ordering from has some fancy policy. If you want, they can prepare food right in your apartment. They deliver the food and use your kitchen to cook. You can actually watch what is going on. Another option is that they will make it at the restaurant beforehand and deliver the food prepared.

Program Model

Now we try to place this situation into programming.
First, I will create a class Restaurant with static function MakeFood, which receives a string order and returns a string indicating that the order is done. This process can take a long time so that is why I placed Thread.Sleep method. Here is the code:

/// <summary>
/// Just a restaurant
/// </summary>
public class Restaurant
{
	/// <summary>
	/// Static function excepts an order and delivers some food
	/// </summary>
	/// <param name="order">
	/// <returns>
	public static string MakeFood(string order)
	{
		//register start:
		Console.WriteLine("Making {0} started at {1}", order, 
				DateTime.Now.ToLongTimeString());

		//food preparation:
		Thread.Sleep(4000);

		//register finish
		Console.WriteLine("Making {0} finished at {1}", order, 
				DateTime.Now.ToLongTimeString());

		//deliver:
		return order.ToUpper() + " made";
	}
}

The next PartyHost class has several public functions which are actually the tasks that should be done while preparing for the party.
Every function registers via console a start and an end time. I also placed some time delay mimicking real life delays.
The class does not have MakeFood function, instead a delegate called Restaurant MakeFood function will be used.
In real life, this delegate will be the phone used to order some food from a restaurant, that is why I called the delegate this way.

public delegate string OrderHandle(string s); 
/// <summary>
/// Party Host
/// </summary>
public class PartyHost
{
	/// <summary>
	/// Person Name
	/// </summary>
	public string Name { get; private set; }

	/// <summary>
	/// constructor
	/// </summary>
	/// <param name="name">
	public PartyHost(string name)
	{
		this.Name = name;
	}

	/// <summary>
	/// Clean up place for party
	/// </summary>
	public void CleanUpPlace()
	{
		//register start:
		Console.WriteLine("Cleaning  started at {0}", 
				DateTime.Now.ToLongTimeString());

		//cleaning:
		Thread.Sleep(3000);

		//register end:
		Console.WriteLine("Cleaning  finished at {0}", 
				DateTime.Now.ToLongTimeString());
	}

	/// <summary>
	/// Set up furniture for the party
	/// </summary>
	public void SetupFurniture()
	{
		//register start:
		Console.WriteLine("Furniture setup  started at {0}" , 
				DateTime.Now.ToLongTimeString());

		//setting up:
		Thread.Sleep(2000);

		//register end:
		Console.WriteLine("Furniture setup  finished at {0}", 
				DateTime.Now.ToLongTimeString());
	}

	/// <summary>
	/// Take your time
	/// </summary>
	public void TakeBathAndDressUp()
	{
		//register start:
		Console.WriteLine("TakeBathAndDressUp  started at {0}", 
				DateTime.Now.ToLongTimeString());

		//having fun:
		Thread.Sleep(2000);

		//register end:
		Console.WriteLine("TakeBathAndDressUp  finished at {0}", 
				DateTime.Now.ToLongTimeString());
	}

	/// <summary>
	/// Get restaurant's phone number
	/// </summary>
	public OrderHandle GetRestaurantPhoneNumber()
	{
		//find the restaurant's phone number in the phone book:
		OrderHandle phone = new OrderHandle(Restaurant.MakeFood);

		return phone;
	}
}

The next four classes are programs. All of them have Main procedure. If you place them into the same project, you have to specify the starting program before you run it.
We will start from the program that calls the delegate directly using Invoke methods. It is a synchronous call.

public class Sync
{
	/// <summary>
	/// Invoking directly
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start time:
		long start = DateTime.Now.Ticks;

		//initialize host
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//clean the place:
		host.CleanUpPlace();

		//set up furniture
		host.SetupFurniture();

		//call food chef into your apartment to prepare food
		//the chef will come and will make the food at your place
		string getFood = restaurantPhone.Invoke("sushi");

		//register when food is done:
		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());

		//prepare yourself:
		host.TakeBathAndDressUp();

		//mark end time:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}
}

Picture 1 displays this case:

SyncCall.gif

When you use a synchronous call, you instruct your compiler to execute the delegate's function within the main thread of execution. I would associate this with inviting the cook from the restaurant to my apartment to prepare the food. This implies the sequential scenario. Each function call in the Main program goes after the previous call is finished. When the program calls the MakeFood function, it cannot call the next function until the current function returns. At the end, the program calculates the total time. In this case, it is 11 seconds.

The second case is an asynchronous call.
Rather than call the delegate's Invoke method, the program calls BeginInvoke method.
There is a difference in the calls.

IAsyncResult asyncResult = restaurantPhone.BeginInvoke("sushi", null, null); 

As you see, the BeginInvoke method's return value is an IasyncResult object. I compare this with the receipt you receive after the food was ordered. You will use this receipt to identify your order later when you want to get it.
This method also requires two additional parameters to the parameter(s) specified by a delegate's signature.
We are not paying attention to these two parameters now, but we will talk about them later. In the mean time, we can supply nulls instead of them.
So first, we called BeginInvoke and got a receipt and after that we called EndInvoke, passed the receipt into the call and got a result (returned value according the delegate's signature).

public class AsyncNoCallBackBadTiming
{
	/// <summary>
	/// Invoking Async No Call Back Bad Timing
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start:
		long start = DateTime.Now.Ticks;

		//initialize host:
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//clean up:
		host.CleanUpPlace();

		//set furniture:
		host.SetupFurniture();

		//call the restaurant to order food and get a token 
		//from them identifying 
		//your order. they immediately start preparing food:
		IAsyncResult asyncResult = restaurantPhone.BeginInvoke
					("sushi", null, null);

		string getFood;

		//call the restaurant, provide the token, get the food, 
		//don't be surprised that
		//you have to wait until the food is ready.
		getFood = restaurantPhone.EndInvoke(asyncResult);

		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());

		//take care of yourself:
		host.TakeBathAndDressUp();

		//mark the end:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}
}

Picture 2 displays this case:

AsyncDelegateCallBad.gif

When you use the asynchronous call, you instruct your compiler to get an available Thread out of a threads pool (or to create a new thread). Rather than prepare the food at my apartment, I use the restaurant facility to make food and later to deliver it to me.
This has a big advantage. The function was called and its execution goes to some other place. The main thread does not need to wait until the function returns. It can proceed to the next function call.

Unfortunately, I misjudged the situation and called EndInvoke right after I called BeginInvoke.
The main thread is waiting until EndInvoke returns. To return EndInvoke function has to wait until the MakeFood function returns. This blocks the main thread and all the advantages of using the asynchronous call are gone.
The total time is the same 11 seconds.

In the third case, we fixed this logic error.
Now the food order goes first. While food is being prepared, I can do some other things, like clean the apartment, set up the furniture.
So the BeginInvoke was called, the main thread immediately receives back the execution flow, some other function was called. By the time EndInvoke is called, the food is ready.
Now the total time of execution is 7 seconds.
We saved 4 seconds!!!

public class AsyncNoCallBackGoodTiming
{
    /// <summary>
	/// Invoking Async No Call Back Good Timing
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start:
		long start = DateTime.Now.Ticks;

		//initialize host:
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//call the restaurant to order food and get a token from them 
		//identifying your order. they immediately start preparing food:
		IAsyncResult asyncResult = restaurantPhone.BeginInvoke
					("sushi", null, null);

		//clean up:
		host.CleanUpPlace();

		//set furniture:
		host.SetupFurniture();

		string getFood;

		//call the restaurant, provide the token, get the food
		getFood = restaurantPhone.EndInvoke(asyncResult);

		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());

		//take care of yourself:
		host.TakeBathAndDressUp();

		//mark the end:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}
}

Here is picture 3 explaining this case:

AsyncDelegateCallGood.gif

The last case explains how to use the CallBack function.
As I mentioned earlier, BeginInvoke function receives two additional delegate's signature parameters.
First parameter is a CallBackFunction delegate.
I used a static function:

private static void FoodDeliveryNotification(IAsyncResult result){} 

Look at the signature. It returns void and accepts IAsyncResult parameter.
Because this parameter is a delegate, I used a new keyword to create a new instance of the delegate and point it to the FoodDeliveryNotification function:

restaurantPhone.BeginInvoke("sushi", 
	new AsyncCallback(FoodDeliveryNotification), restaurantPhone); 

The second parameter is an object and I can pass any object I want. Here I passed the calling delegate itself. As we already know, the execution of MakeFood is done on a different thread.
When the function returns, it triggers the callback function FoodDeliveryNotification. Several things happen within the function.

  1. It gets the calling delegate.
  2. It calls the calling delegate's EndInvoke function passing IasyncResult.
  3. It handles the returned value.

In this case, my main program does not directly call EndInvoke. It is neatly done through CallBack function.

public class AsyncCallBack
{
        /// <summary>
	/// Invoking Async Call Back
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start:
		long start = DateTime.Now.Ticks;

		//initialize host:
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//call the restaurant to order food and get a token 
		//from them identifying your order. 
		//They immediately start preparing food:
		restaurantPhone.BeginInvoke("sushi", new AsyncCallback
			(FoodDeliveryNotification), restaurantPhone);

		//clean up:
		host.CleanUpPlace();

		//set furniture:
		host.SetupFurniture();

		//take care of yourself:
		host.TakeBathAndDressUp();

		//mark the end:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}

	private static void FoodDeliveryNotification(IAsyncResult result)
	{
		//get the delegate:
		OrderHandle handle = (OrderHandle)result.AsyncState;

		//call end invoke:
		string getFood = handle.EndInvoke(result);

		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());
	}
}
}

Summary

In this article, you studied how to asynchronously call a delegate. Even though you know how to do it, you do not have to do it. Usually, it makes sense when the delegate calls an IO function, which is time consuming and can potentially delay your program. But use your judgment on how to place the calls within your main program. If placed incorrectly, it will delay your program and no advantages will be achieved.

To be continued...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here