Asynchronous Method Invocation
Written by Thong D. Nguyen Friday, 02 July 2010 09:29
Blog - Programming
| Article Index |
|---|
| Asynchronous Method Invocation |
| Page 2 |
| All Pages |
Introduction
In this article, I am going to explain asynchronous method calls and how to use them. After playing with delegates, threads, and asynchronous invocation for so long, it would be a sin not to share some of my wisdom and knowledge on the subject, so hopefully, you won’t be looking at an MSDN article at 1 AM wondering why you decided to go into computers. I will try to use baby steps and lots of examples… Overall, I will cover how to call methods asynchronously, how to pass parameters to such methods, and how to find out when a method completes execution. Finally, I will show the Command Pattern in use for simplifying some of the code.
The big advantage with .NET asynchronous method invocation is that you can take any method you have in your project, and you can call it asynchronously without touching the code of your method. Although most of the magic is within the .NET framework, it is important to see what is going on in the back, and that’s what we are going to study here.
Synchronous vs. Asynchronous
Let me try to explain synchronous and asynchronous method invocations with an example, because I know people on The Code Project like to see code and not read War and Peace (not that I have anything against this book).
Synchronous method invocation
Suppose we have a function Foo() that takes 10 seconds to execute.
private void Foo()
{
// sleep for 10 seconds.
Thread.Sleep(10000);
}
Normally, when your application calls the function Foo(), it will need to wait 10 seconds until Foo() is finished and control is returned to the calling thread. Now, suppose you want to call Foo() 100 times, then we know that it would take 1000 seconds for the control to return to the calling thread. This type of a method invocation is Synchronous.
- Call
Foo() Foo()is executed- Control goes back to the calling thread
Let's now call Foo() using delegates because most of the work we will do here is based on delegates. Luckily for us, there is already a delegate within the .NET framework that allows us to call a function that takes no parameter and has no return value. The delegate is called MethodeInvoker. Let's play with it a little.
// create a delegate of MethodInvoker poiting
// to our Foo() function.
MethodInvoker simpleDelegate = new MethodInvoker(Foo);
// Calling Foo
simpleDelegate.Invoke();
Even with the example above, we are still calling Foo() synchronously. The calling thread still needs to wait for the Invoke() function to complete until the control is returned to the calling thread.
Asynchronous method invocation
But what if I wanted to call Foo() and not wait for it to finish executing? In fact, to make things interesting, what if I didn’t care when it is finished? Let’s say, I just wanted to call Foo 100 times without waiting for any of the function calls to complete. Basically, doing something called Fire and Forget. You call the function, you don’t wait for it, and you just forget about it. And… let’s not forget! I am not willing to change a line of code in my super complicated fancy Foo() function.
// create a delegate of MethodInvoker poiting to
// our Foo function.
MethodInvoker simpleDelegate = new MethodInvoker(Foo);
// Calling Foo Async
for(int i=0; i<100; i++)
simpleDelegate.BeginInvoke(null, null);
Let me make a few comments about the code above.
- Notice that
BeginInvoke()is the line of code that executes theFoo()function. However, the control is returned to the caller right away, without waiting forFoo()to complete. - The code above does not know when a call to
Foo()completes, I will cover that later. BeginInvoke()is used instead ofInvoke(). For now, don’t worry about the parameters this function takes; I will cover that in more detail later.
What is the magic that .NET is doing in the background
Once you ask the framework to call something asynchronously, it needs a thread to do the work. It can not be the current thread, because that would make the invocation synchronous (blocking). Instead, the runtime queues a request to execute the function on a thread from the .NET Thread Pool. You don’t really need to code anything for it, all of it happens in the background. But, just because it is all transparent doesn’t mean you should care about it. There are a few things to remember:
Foo()is executed on a separate thread, a thread that belongs to the .NET Thread Pool.- A .NET Thread Pool normally has 25 threads in it (you can change that limit), and each time
Foo()is called, it is going to be executed on one of these threads. You can't control which one. - The Thread Pool has its limits! Once all the threads are used, an async method invocation is queued until one of the threads from the pool is freed. This is called Thread Pool Starvation, and normally when it comes to that, performance is compromised.
Don’t dive too deep into the thread pool, you might run out of oxygen!
So, let's see an example of when the Thread Pool is starved. Let's modify our Foo function to wait for 30 seconds, and also let it report the following:
- Number of avaible threads on the pool
- If the thread is on the thread pool
- The thread ID.
We know that initially the thread pool contains 25 threads, so I am going to call my Foo function asynchronously 30 times (to see what happens after the 25th call).
private void CallFoo30AsyncTimes()
{
// create a delegate of MethodInvoker
// poiting to our Foo function.
MethodInvoker simpleDelegate =
new MethodInvoker(Foo);
// Calling Foo Async 30 times.
for (int i = 0; i < 30; i++)
{
// call Foo()
simpleDelegate.BeginInvoke(null, null);
}
}
private void Foo()
{
int intAvailableThreads, intAvailableIoAsynThreds;
// ask the number of avaialbe threads on the pool,
//we really only care about the first parameter.
ThreadPool.GetAvailableThreads(out intAvailableThreads,
out intAvailableIoAsynThreds);
// build a message to log
string strMessage =
String.Format(@"Is Thread Pool: {1},
Thread Id: {2} Free Threads {3}",
Thread.CurrentThread.IsThreadPoolThread.ToString(),
Thread.CurrentThread.GetHashCode(),
intAvailableThreads);
// check if the thread is on the thread pool.
Trace.WriteLine(strMessage);
// create a delay...
Thread.Sleep(30000);
return;
}
Output window:
Is Thread Pool: True, Thread Id: 7 Free Threads 24
Is Thread Pool: True, Thread Id: 12 Free Threads 23
Is Thread Pool: True, Thread Id: 13 Free Threads 22
Is Thread Pool: True, Thread Id: 14 Free Threads 21
Is Thread Pool: True, Thread Id: 15 Free Threads 20
Is Thread Pool: True, Thread Id: 16 Free Threads 19
Is Thread Pool: True, Thread Id: 17 Free Threads 18
Is Thread Pool: True, Thread Id: 18 Free Threads 17
Is Thread Pool: True, Thread Id: 19 Free Threads 16
Is Thread Pool: True, Thread Id: 20 Free Threads 15
Is Thread Pool: True, Thread Id: 21 Free Threads 14
Is Thread Pool: True, Thread Id: 22 Free Threads 13
Is Thread Pool: True, Thread Id: 23 Free Threads 12
Is Thread Pool: True, Thread Id: 24 Free Threads 11
Is Thread Pool: True, Thread Id: 25 Free Threads 10
Is Thread Pool: True, Thread Id: 26 Free Threads 9
Is Thread Pool: True, Thread Id: 27 Free Threads 8
Is Thread Pool: True, Thread Id: 28 Free Threads 7
Is Thread Pool: True, Thread Id: 29 Free Threads 6
Is Thread Pool: True, Thread Id: 30 Free Threads 5
Is Thread Pool: True, Thread Id: 31 Free Threads 4
Is Thread Pool: True, Thread Id: 32 Free Threads 3
Is Thread Pool: True, Thread Id: 33 Free Threads 2
Is Thread Pool: True, Thread Id: 34 Free Threads 1
Is Thread Pool: True, Thread Id: 35 Free Threads 0
Is Thread Pool: True, Thread Id: 7 Free Threads 0
Is Thread Pool: True, Thread Id: 12 Free Threads 0
Is Thread Pool: True, Thread Id: 13 Free Threads 0
Is Thread Pool: True, Thread Id: 14 Free Threads 0
Is Thread Pool: True, Thread Id: 15 Free Threads 0
Let’s make a few notes about the output:
- Notice, first of all, that all the threads are on the thread pool.
- Notice that each time
Foois called, another thread ID is assigned. However, you can see that some of the threads are recycled. - After calling
Foo()25 times, you can see that there are no more free threads on the pool. At this point, the application “waits” for a free thread. - Once a thread is freed, the program grabs it right away, calling
Foo(), and still there are 0 free threads on the pool. This continues to happen untilFoo()is called 30 times.
So right away, not doing anything too fancy, we can make a few comments about calling methods asynchronously.
- Know that your code will run in a separate thread, so some thread safety issues may apply. This is a topic on its own, and I will not cover it here.
- Remember that the pool has its limits. If you plan to call many functions asynchronously and if they take a long time to execute, Thread Pool Starvation might occur.
BeginInvoke() and EndInvoke()
So far we saw how to invoke a method without really knowing when it is finished. But with EndInvoke(), it is possible to do a few more things. First of all, EndInvoke will block until your function completes execution; so, calling BeginInvoke followed by EndInvoke is really almost like calling the function in a blocking mode (because the EndInvoke will wait until the function completes). But, how does the .NET runtime know how to bind a BeginInvoke with an EndInvoke? Well, that’s where IAsyncResult comes in. When calling BegineInvoke, the return object is an object of type IAsyncResult; it is the glue that allows the framework to track your function execution. Think of it like a little tag to let you know what is going on with your function. With this little powerful super tag, you can find out when your function completes execution, and you can also use this tag to attach any state object you might want to pass to your function. Okay! Let’s see some examples so this doesn't become too confusing... Let's create a new Foo function.
private void FooOneSecond()
{
// sleep for one second!
Thread.Sleep(1000);
}
private void UsingEndInvoke()
{
// create a delegate of MethodInvoker poiting to our Foo function.
MethodInvoker simpleDelegate = new MethodInvoker(FooOneSecond);
// start FooOneSecond, but pass it some data this time!
// look at the second parameter
IAsyncResult tag =
simpleDelegate.BeginInvoke(null, "passing some state");
// program will block until FooOneSecond is complete!
simpleDelegate.EndInvoke(tag);
// once EndInvoke is complete, get the state object
string strState = (string)tag.AsyncState;
// write the state object
Trace.WriteLine("State When Calling EndInvoke: "
+ tag.AsyncState.ToString());
}
What about Exceptions, how do I catch them?
Now, let's make it a little more complicated. Let me modify the FooOneSecond function and make it throw an exception. Now, you should be wondering how you will catch this exception. In the BeginInvoke, or in the EndInvoke? Or is it even possible to catch this exception? Well, it is not in the BeginInvoke. The job of BeginInvoke is to simply start the function on the ThreadPool. It is really the job of the EndInvoke to report all the information about the completion of the function, and this includes exceptions. Notice the next snippet of code:
private void FooOneSecond()
{
// sleep for one second!
Thread.Sleep(1000);
// throw an exception
throw new Exception("Exception from FooOneSecond");
}
Now, let's call FooOneSecond and see if we can catch the exception.
private void UsingEndInvoke()
{
// create a delegate of MethodInvoker poiting
// to our Foo function.
MethodInvoker simpleDelegate =
new MethodInvoker(FooOneSecond);
// start FooOneSecond, but pass it some data this time!
// look at the second parameter
IAsyncResult tag = simpleDelegate.BeginInvoke(null, "passing some state");
try
{
// program will block until FooOneSecond is complete!
simpleDelegate.EndInvoke(tag);
}
catch (Exception e)
{
// it is here we can catch the exception
Trace.WriteLine(e.Message);
}
// once EndInvoke is complete, get the state object
string strState = (string)tag.AsyncState;
// write the state object
Trace.WriteLine("State When Calling EndInvoke: "
+ tag.AsyncState.ToString());
}
By running the code, you will see that the exception is only thrown and caught when calling EndInvoke. If you decide to never call EndInvoke, then you will not get the exception. However, when running this code within the debugger, depending on your exception settings, your debugger might stop when throwing the exception. But that is the debugger. Using a release version, if you don’t call EndInvoke, you will never get the exception.
Passing parameters to your method
Okay, so calling functions without parameters is not going to take us very far, so I am going to modify my super fancy and sophisticated Foo function to take a few parameters.
private string FooWithParameters(string param1,
int param2, ArrayList list)
{
// lets modify the data!
param1 = "Modify Value for param1";
param2 = 200;
list = new ArrayList();
return "Thank you for reading this article";
}
Let's call FooWithParameters using BeginInvoke and EndInvoke. First of all, before we do anything, we must have a delegate that matches the signature of this method.
public delegate string DelegateWithParameters(string param1,
int param2, ArrayList list);
Think of BeginInvoke and EndInvoke as cutting our function into two separate methods. The BeginInvoke is responsible for accepting all the input parameters followed by two additional parameters every BeginInvoke has (callback delegate, and a state object). The EndInvoke is responsible for returning all output parameters (parameters marked with ref or out) and a return value, if there is one. Let's go back into our example to find out what are considered input parameters and what are output parameters. param1, param2, and list are all considered input parameters, and therefore, they will be accepted as arguments to the BeginInvoke method. The return value of type string is considered an output parameter, and therefore, it will be the return type for EndInvoke. The cool thing is that the compiler is able to generate the correct signature for BeginInvoke and EndInvoke based on the declaration of your delegate. Notice that I decided to modify the values of my input parameters just to examine if the behaviour is as I expect it to be without calling BeginInvoke and EndInvoke. I also re-allocate the ArrayList that is passed to a new ArrayList. So, try to guess what the output is going to be...
private void CallFooWithParameters()
{
// create the paramets to pass to the function
string strParam1 = "Param1";
int intValue = 100;
ArrayList list = new ArrayList();
list.Add("Item1");
// create the delegate
DelegateWithParameters delFoo =
new DelegateWithParameters(FooWithParameters);
// call the BeginInvoke function!
IAsyncResult tag =
delFoo.BeginInvoke(strParam1, intValue, list, null, null);
// normally control is returned right away,
// so you can do other work here...
// calling end invoke to get the return value
string strResult = delFoo.EndInvoke(tag);
// write down the parameters:
Trace.WriteLine("param1: " + strParam1);
Trace.WriteLine("param2: " + intValue);
Trace.WriteLine("ArrayList count: " + list.Count);
}
Let's see our FooWithParameters again, just so you don't need to scroll up.
private string FooWithParameters(string param1,
int param2, ArrayList list)
{
// lets modify the data!
param1 = "Modify Value for param1";
param2 = 200;
list = new ArrayList();
return "Thank you for reading this article";
}
Let me give you the three lines from the output window after calling EndInvoke():
param1: Param1
param2: 100
ArrayList count: 1
Okay, let’s analyze all this. Even when my function modifies the values of the input parameters, we don’t get to see those changes after calling EndInvoke. The string is a mutable type, therefore, a copy of the string is created, and the change is not passed back to the caller. Integers are value types, and they create a copy when passed by value. Finally, re-creating the ArrayList is not returned to the caller because the reference to the ArrayList is passed by value, and in fact, re-creating the ArrayList is simply creating a new allocation for ArrayList assigning the "copied" reference that was passed. In fact, that reference is lost, and normally considered as a memory leak; but luckily for us, the .NET garbage collector will eventually grab it. So, what if we wanted to get back our new allocated ArrayList and the rest of the changes we did to our parameters? What do we need to do? Well, it is simple; we simply have to tag the ArrayList as a ref parameter. Just for fun, let’s also add output parameters just to show how EndInvoke is changed.
private string FooWithOutAndRefParameters(string param1,
out int param2, ref ArrayList list)
{
// lets modify the data!
param1 = "Modify Value for param1";
param2 = 200;
list = new ArrayList();
return "Thank you for reading this article";
}
Let us see what is considered an output parameter and what is considered an input parameter…
Param1is an input parameter, it will only be accepted withinBeginInvoke.Param2is input and output; therefore, it will be passed to bothBeginInvokeandEndInvoke(EndInvokewill give us the updated value).listis passed by reference, and therefore, it is too going to be passed to bothBeginInvokeandEndInvoke.
Let’s see how our delegate looks like now:
public delegate string DelegateWithOutAndRefParameters(string param1,
out int param2, ref ArrayList list);
and finally, let's look at the function that calls FooWithOutAndRefParameters:
private void CallFooWithOutAndRefParameters()
{
// create the paramets to pass to the function
string strParam1 = "Param1";
int intValue = 100;
ArrayList list = new ArrayList();
list.Add("Item1");
// create the delegate
DelegateWithOutAndRefParameters delFoo =
new DelegateWithOutAndRefParameters(FooWithOutAndRefParameters);
// call the beginInvoke function!
IAsyncResult tag =
delFoo.BeginInvoke(strParam1,
out intValue,
ref list,
null, null);
// normally control is returned right away,
// so you can do other work here...
// calling end invoke notice that intValue and list are passed
// as arguments because they might be updated within the function.
string strResult =
delFoo.EndInvoke(out intValue, ref list, tag);
// write down the parameters:
Trace.WriteLine("param1: " + strParam1);
Trace.WriteLine("param2: " + intValue);
Trace.WriteLine("ArrayList count: " + list.Count);
Trace.WriteLine("return value: " + strResult);
}
Here is the output:
param1: Param1
param2: 200
ArrayList count: 0
return value: Thank you for reading this article
Notice that param1 does not change. It is an input parameter, and param2 was passed as an output parameter and was updated to 200. The array list has been reallocated, and now we see that it is pointing to a new reference of zero elements (the original reference is lost). I hope that now you understand how parameters are passed with BeginInvoke and EndInvoke. Let’s move on to looking at how to be notified if a non-blocking function is completed.
| < Prev | Next > |
|---|
Idioms
- Diamond cut diamond (Vỏ quýt dày móng tay nhọn)
- Don't count your chickens, before they are hatched. (Chưa đỗ ông Nghè đã đe Hàng tổng)
Who's online
Location
38.107.179.216
38.107.179.216Search Bot
unknown unkno

OS
PHP
MySQL
Time
Caching
GZIP
Members
Content
Web Links





Today
Yesterday
This week
Last week
This month
Last month
All days
