Monday, January 28, 2008
Calling any method asynchronously via Delegate.BeginInvoke
C# and .NET provide a variety of ways to execute code asynchronously. You could, for example, execute a method via a
We are all familiar with
A delegate can be use to easily execute any method on a background thread, even if that method has
To execute the method asynchronously using a
Next, we simply create an instance of the delegate, and call
Ok, now for the callback. When
You may be asking if the callback only takes an
There are a few things to notice above. For starters, because the
Secondly, note that you do not have access to parameter
Thirdly, keep in mind that the method is executed on a background thread, and that the callback will occur on that backrgound thread. Therefore, you have all of the normal threading things to worry about. For example, let's say parameter
ThreadStart. You could also trap the DoWork event of a BackgroundWorker component. A seemingly simpler way, however, is to simply use the BeginInvoke method of a delegate.We are all familiar with
BeginInvoke: it's what we C#.NET programmers use all the time to schedule something to be executed on the main UI thread, right? Well, not exactly. BeginInvoke causes something to be executed on an appropriate thread, and it just so happens that the appropriate thread for Control.BeginInvoke happens to be the thread on which the control's Window handle was created. And, very convieniently for us, the appropriate thread for a Delegate.BeginInvoke happens to be a background worker thread.A delegate can be use to easily execute any method on a background thread, even if that method has
ref parameters, out parameters and returns a value or throws an exception. Consider the method below.
string MyMethod(string p1, ref string p2, out string p3)
{
// Let's assume that this is some code that can
// 1) Takes a long time to execute
// 2) Uses p1 and p2 as inputs
// 3) Returns p2, p3 and a return value
// 4) Can throw exceptions
...
}
To execute the method asynchronously using a
Delegate.BeginInvoke, we first need to define a delegate that has the same signature of the method to be executed on the background thread.
delegate string MyMethodDelegate(string p1, ref string p2, out string p3);
Next, we simply create an instance of the delegate, and call
BeginInvoke on that delegate. We pass all of the parameters into the delegate, as well as an instance of an AsyncCallback that points to the method that should be called when the invoked method has completed. We also pass a reference to the delegate as the AsyncState. The AsyncState can be any object, but I'll explain why we should use the delegate in a minute.
MyMethodDelegate myDelegate = new MyMethodDelegate(MyMethod);
myDelegate.BeginInvoke(
p1, ref p2, out p3,
new AsyncCallback(MyMethodCallback),
myDelegate ); // Note that we are passing the delegate as the AsyncState!
Ok, now for the callback. When
MyMethod is done being invoke on a background thread -- whether it succeeds or throws an exception -- the method MyMethodCallback will be called. Since MyMethodCallback is pointed to by an AsyncCallback, it must take exactly one IAsyncResult parameter and return void.
void MyMethodCallback(IAsyncResult ar)
{
...
}
You may be asking if the callback only takes an
IAsyncResult and only returns void, how do we get the return values back from MyMethod? Simple! Remember how we passed the instance of myDelegate as the AsyncState? We can get it back from the IAsyncResult.AsyncState cast back as the delegate, then call EndInvoke on that delegate to get p2, p3 and return value, and even trap an exception that may have been thrown.
void MyMethodCallback(IAsyncResult ar)
{
MyMethodDelegate myDelegate = ar.AsyncState as MyMethodDelegate;
string p2 = null;
string p3;
try
{
string result = myDelegate.EndInvoke(ref p2, out p3, ar);
}
catch
{
// Any exceptions thrown by MyDelegate while it was executed
// on the background thread can be caught here.
}
}
There are a few things to notice above. For starters, because the
p2 parameter is a reference parameter, it must be initialized before it can be passed into EndInvoke. But, whatever it is initialized to is totally irrelevant because it is only being passed into EndInvoke to get the modified value back out. You can initialize p2 to anything, as long as you initialize it to something (including null) so that the compiler does not complain.Secondly, note that you do not have access to parameter
p1 from within MyMethodCallback. And, since the delegate was passed as the AsyncState you cannot pass p1 as the AsyncState. Therefore, if the callback needs access to any values that are not returned as reference parameters, out parameters or method returns values you will either have to store them as instance member variables, or perharps pass some object that includes the delegate and those variables as the AsyncState instead of just the delegate.Thirdly, keep in mind that the method is executed on a background thread, and that the callback will occur on that backrgound thread. Therefore, you have all of the normal threading things to worry about. For example, let's say parameter
p1 was an instance of some object other than a string. It would be quite possible that some other thread could have changed a value of a property on the object after BeginInvoke was called but before the method was actually exected; even worse the value of that property could be changed while the invoked method is still executing.Subscribe to Posts [Atom]