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.
- Clean up the room
- Make some food
- Set the table
- 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:
public class Restaurant
{
public static string MakeFood(string order)
{
Console.WriteLine("Making {0} started at {1}", order,
DateTime.Now.ToLongTimeString());
Thread.Sleep(4000);
Console.WriteLine("Making {0} finished at {1}", order,
DateTime.Now.ToLongTimeString());
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);
public class PartyHost
{
public string Name { get; private set; }
public PartyHost(string name)
{
this.Name = name;
}
public void CleanUpPlace()
{
Console.WriteLine("Cleaning started at {0}",
DateTime.Now.ToLongTimeString());
Thread.Sleep(3000);
Console.WriteLine("Cleaning finished at {0}",
DateTime.Now.ToLongTimeString());
}
public void SetupFurniture()
{
Console.WriteLine("Furniture setup started at {0}" ,
DateTime.Now.ToLongTimeString());
Thread.Sleep(2000);
Console.WriteLine("Furniture setup finished at {0}",
DateTime.Now.ToLongTimeString());
}
public void TakeBathAndDressUp()
{
Console.WriteLine("TakeBathAndDressUp started at {0}",
DateTime.Now.ToLongTimeString());
Thread.Sleep(2000);
Console.WriteLine("TakeBathAndDressUp finished at {0}",
DateTime.Now.ToLongTimeString());
}
public OrderHandle GetRestaurantPhoneNumber()
{
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
{
static void Main(string[] args)
{
long start = DateTime.Now.Ticks;
PartyHost host = new PartyHost("Ed");
OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();
host.CleanUpPlace();
host.SetupFurniture();
string getFood = restaurantPhone.Invoke("sushi");
Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());
host.TakeBathAndDressUp();
long end = DateTime.Now.Ticks;
Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);
Console.Read();
}
}
Picture 1 displays this case:
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 null
s 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
{
static void Main(string[] args)
{
long start = DateTime.Now.Ticks;
PartyHost host = new PartyHost("Ed");
OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();
host.CleanUpPlace();
host.SetupFurniture();
IAsyncResult asyncResult = restaurantPhone.BeginInvoke
("sushi", null, null);
string getFood;
getFood = restaurantPhone.EndInvoke(asyncResult);
Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());
host.TakeBathAndDressUp();
long end = DateTime.Now.Ticks;
Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);
Console.Read();
}
}
Picture 2 displays this case:
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
{
static void Main(string[] args)
{
long start = DateTime.Now.Ticks;
PartyHost host = new PartyHost("Ed");
OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();
IAsyncResult asyncResult = restaurantPhone.BeginInvoke
("sushi", null, null);
host.CleanUpPlace();
host.SetupFurniture();
string getFood;
getFood = restaurantPhone.EndInvoke(asyncResult);
Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());
host.TakeBathAndDressUp();
long end = DateTime.Now.Ticks;
Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);
Console.Read();
}
}
Here is picture 3 explaining this case:
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.
- It gets the calling delegate.
- It calls the calling delegate's
EndInvoke
function passing IasyncResult
.
- 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
{
static void Main(string[] args)
{
long start = DateTime.Now.Ticks;
PartyHost host = new PartyHost("Ed");
OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();
restaurantPhone.BeginInvoke("sushi", new AsyncCallback
(FoodDeliveryNotification), restaurantPhone);
host.CleanUpPlace();
host.SetupFurniture();
host.TakeBathAndDressUp();
long end = DateTime.Now.Ticks;
Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);
Console.Read();
}
private static void FoodDeliveryNotification(IAsyncResult result)
{
OrderHandle handle = (OrderHandle)result.AsyncState;
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...