|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn 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. AsynchronousLet 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 invocationSuppose we have a function private void Foo()
{
// sleep for 10 seconds.
Thread.Sleep(10000);
}
Normally, when your application calls the function
Let's now call // 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 Asynchronous method invocationBut what if I wanted to call // 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.
What is the magic that .NET is doing in the backgroundOnce 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:
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
We know that initially the thread pool contains 25 threads, so I am going to call my 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:
So right away, not doing anything too fancy, we can make a few comments about calling methods asynchronously.
BeginInvoke() and EndInvoke()So far we saw how to invoke a method without really knowing when it is finished. But with 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 private void FooOneSecond()
{
// sleep for one second!
Thread.Sleep(1000);
// throw an exception
throw new Exception("Exception from FooOneSecond");
}
Now, let's call 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 Passing parameters to your methodOkay, so calling functions without parameters is not going to take us very far, so I am going to modify my super fancy and sophisticated 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 public delegate string DelegateWithParameters(string param1,
int param2, ArrayList list);
Think of 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 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 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 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…
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 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 What they don’t want you to know about IAsyncResultYou should be wondering how
Now, if we dig a little deeper, we will find that I had to shrink the above image so you won’t need to scroll to the right to read it, you can simply click on the image to view it We can clearly see our return value, our output parameter, and ourref parameters. There is even an exception property to hold the exception. Notice that I expanded, in the debug window for OutArgs to show, the value 200 and a reference to the newly allocated ArrayList. You can also see in the property ReturnValue, the string “Thank you for reading this article”. If we had an exception, then the EndInvoke would throw it for us to catch it. I think this is proof enough to conclude that all the information regarding your function call is saved with that little IAsyncResult object you get back from BeginInvoke, it is like a key to your data. If we lose this object, we will never know our output parameters, ref parameters, and return value. It will also not be possible to catch an exception without this object. It’s the key! You lose it, and the info is lost forever in the maze of the .NET runtime… OK, getting a little carried away here. I think I made my point.
Using the Callback delegate, Hollywood style "Don’t call me I will call you!"At this point, you should understand how parameters can be passed, how to pass state, and understand the fact that your method is executed on a thread within the private void CallFooWithOutAndRefParametersWithCallback()
{
// 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);
delFoo.BeginInvoke(strParam1,
out intValue,
ref list,
new AsyncCallback(CallBack), // callback delegate!
null);
}
private void CallBack(IAsyncResult ar)
{
// define the output parameter
int intOutputValue;
ArrayList list = null;
// first case IAsyncResult to an AsyncResult object, so we can get the
// delegate that was used to call the function.
AsyncResult result = (AsyncResult)ar;
// grab the delegate
DelegateWithOutAndRefParameters del =
(DelegateWithOutAndRefParameters) result.AsyncDelegate;
// now that we have the delegate,
// we must call EndInvoke on it, so we can get all
// the information about our method call.
string strReturnValue = del.EndInvoke(out intOutputValue,
ref list, ar);
}
In here, you can see that we passed a delegate to the function AsyncResult result = (AsyncResult)ar;
// grab the delegate
DelegateWithOutAndRefParameters del =
(DelegateWithOutAndRefParameters) result.AsyncDelegate;
Wait a minute! On which thread is the call-back executed on?After all, the callback is invoked by .NET using your delegate, but still it is .NET that calls this delegate. It is your right and duty to know on which thread your code is executed on. To give a clear picture of what is going on, I decided to yet again modify my private string FooWithOutAndRefParameters(string param1,
out int param2, ref ArrayList list)
{
// log thread information
Trace.WriteLine("In FooWithOutAndRefParameters: Thread Pool? "
+ Thread.CurrentThread.IsThreadPoolThread.ToString() +
" Thread Id: " + Thread.CurrentThread.GetHashCode());
// wait for 4 seconds as if this functions takes a while to run.
Thread.Sleep(4000);
// lets modify the data!
param1 = "Modify Value for param1";
param2 = 200;
list = new ArrayList();
return "Thank you for reading this article";
}
I also added thread information to the callback function: private void CallBack(IAsyncResult ar)
{
// which thread are we on?
Trace.WriteLine("In Callback: Thread Pool? "
+ Thread.CurrentThread.IsThreadPoolThread.ToString() +
" Thread Id: " + Thread.CurrentThread.GetHashCode());
// define the output parameter
int intOutputValue;
ArrayList list = null;
// first case IAsyncResult to an AsyncResult object,
// so we can get the delegate that was used to call the function.
AsyncResult result = (AsyncResult)ar;
// grab the delegate
DelegateWithOutAndRefParameters del =
(DelegateWithOutAndRefParameters) result.AsyncDelegate;
// now that we have the delegate, we must call EndInvoke on it, so we
// can get all the information about our method call.
string strReturnValue = del.EndInvoke(out intOutputValue, ref list, ar);
}
I decided to execute private void button4_Click(object sender, EventArgs e)
{
CallFooWithOutAndRefParametersWithCallback();
}
Let’s see the output after pressing my button thrice (calling the function thrice): In FooWithOutAndRefParameters: Thread Pool? True Thread Id: 7
In FooWithOutAndRefParameters: Thread Pool? True Thread Id: 12
In FooWithOutAndRefParameters: Thread Pool? True Thread Id: 13
In Callback: Thread Pool? True Thread Id: 7
In Callback: Thread Pool? True Thread Id: 12
In Callback: Thread Pool? True Thread Id: 13
Notice that my Using the Command Pattern to clean things up!Okay! Let's face it, things are getting a little messy. We have a public interface ICommand
{
void Execute();
}
It it time that we stop using our useless
Suppose this was our Business Layer. To keep the example simple, I hard-coded what would normally come from a data layer. public class BoCustomer
{
public DataSet GetCustomer(int intCustomerId)
{
// call data layer and get customer information
DataSet ds = new DataSet();
DataTable dt = new DataTable("Customer");
dt.Columns.Add("Id", typeof(int));
dt.Columns.Add("FirstName", typeof(string));
dt.Columns.Add("LastName", typeof(string));
dt.Rows.Add(intCustomerId, "Mike", "Peretz");
ds.Tables.Add(dt);
// lets make this take some time...
System.Threading.Thread.Sleep(2000);
return ds;
}
}
Now, let's create our Command, which is responsible to update the grid based on the customer ID. public class GetCustomerByIdCommand : ICommand
{
private GetCustomerByIdDelegate m_invokeMe;
private DataGridView m_grid;
private int m_intCustmerId;
// notice that the delegate is private,
// only the command can use it.
private delegate DataSet GetCustomerByIdDelegate(int intCustId);
public GetCustomerByIdCommand(BoCustomer boCustomer,
DataGridView grid,
int intCustId)
{
m_grid = grid;
m_intCustmerId = intCustId;
// setup the delegate to call
m_invokeMe =
new GetCustomerByIdDelegate(boCustomer.GetCustomer);
}
public void Execute()
{
// call the method on the thread pool
m_invokeMe.BeginInvoke(m_intCustmerId,
this.CallBack, // callback!
null);
}
private void CallBack(IAsyncResult ar)
{
// get the dataset as output
DataSet ds = m_invokeMe.EndInvoke(ar);
// update the grid a thread safe fasion!
MethodInvoker updateGrid = delegate
{
m_grid.DataSource = ds.Tables[0];
};
if (m_grid.InvokeRequired)
m_grid.Invoke(updateGrid);
else
updateGrid();
}
}
Notice that the
Also notice that the delegate is hidden within the Command object, so the client doesn’t need to know the inner working of the Command. All the client needs to do is build the Command and call private ICommand m_cmdGetCustById;
private void button1_Click(object sender, EventArgs e)
{
// get the custmer id from the screen
int intCustId = Convert.ToInt32(m_txtCustId.Text);
// use the buisness layer to get the data
BoCustomer bo = new BoCustomer();
// create a command that has all the tools to update the grid
m_cmdGetCustById = new GetCustomerByIdCommand(
bo, m_grid, intCustId);
// call the command in a non blocking mode.
m_cmdGetCustById.Execute();
}
Notice, that
ConclusionPhew! It took me almost a week to write this fun article. I tried to cover all the important aspects of calling a method in a non-blocking mode. Here are a few things to remember:
Thank you for reading, have a wonderful day, and happy .NET coding everyone!
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||