"Vote early and often." --The California E-Voting Machine Association
Developers often run into this problem, particularly with WebServices: You have a WebService with one or more methods that make calls to external objects which, for one reason or another, are beyond your control. These are blocking calls. What happens when the remote object is not operational, in a "hung" state, or whatever? You have made a blocking method call that never returns, and this leaves you in a sorry state indeed. Certainly the consumer of your WebService isn't going to be happy. They're sitting around, wondering what the hell happened, and why nothing is coming back, right?
Not only that, but you probably need some way to make notifications of interested parties that your remote object has died, so somebody can get off their hotshot lazy butt and fix it. What you really need is a way to turn that blocking method call into a "BlockButOnlyIfItDoesntTakeMoreThanXSeconds" call.
Delegates to the rescue!
In a much earlier article, "Sending Emails Asynchronously" I showed how developers can wire up any method to be asynchronous by wrapping the calls in a Delegate. Here I will focus on another feature of Delegates - the IAsyncResult object, the WaitHandle, and the overloaded WaitOne method.
Let's say you have a WebMethod that accepts an ArrayList of input items, and makes a remote method call on one of these "not under my control" objects, and then is supposed to return another ArrayList of return items. The signature might look like this:
public ArrayList DoWorkNeedsTimeout( ArrayList alIn)
ArrayList retAl =RemoteObject.DoWork(alIn);
What happens if RemoteObject.DoWork never returns? You're sunk. So how can we wire this up so that we can offer a timeout option?
First, I am going to modify the signature a bit to allow for testing. We'll add a second int parameter, "secondsToWait", to allow us to experiment more easily.
So the first step I take here is to actually create a private "Helper" method that simply makes the call to the remote object. We'll wrap this with a delegate to make it async, instead of making the blocking call that we have now. I'll call it "DoWorkWithTimeout":
private ArrayList DoWorkWithTimeout(ArrayList alin, int secondsToWait)
"TestClass" is a separate class in the WebService, with a static DoWork method, and is simply a surrogate for the external object we call upon, to make experimentation easier.
Next, I need to create a new Delegate whose signature exactly matches my helper method:
private delegate ArrayList DoWorkNeedsTimeoutDelegate(ArrayList alin,int secondsToWait);
Now I am ready to modify the method body of my original WebMethod to make an asynchronous call with a timeout:
public ArrayList DoWorkNeedsTimeout( ArrayList alin, int secondsToWait)
ArrayList alOut = new ArrayList();
//Create an instance of our delegate, pointing to the helper method:
DoWorkNeedsTimeoutDelegate deleg = new DoWorkNeedsTimeoutDelegate (DoWorkWithTimeout);
// Call BeginInvoke on delegate.
// Note on last two parameters of Delegate BeginInvoke Method:
// 1) callback: not used here, we can pass null
// 2) state: not used, pass an instance of object in the required parameter location
// Invoke the delegate passing the parameters and get the IAsyncResult object in "ar":
IAsyncResult ar = deleg.BeginInvoke(alin,secondsToWait, null, new object());
// if the WaitOne method times out before we get a result, it will be false:
if (!ar.AsyncWaitHandle.WaitOne(5000, false))
// handle timeout logging / notification here - Syslog, Database, Email - whatever you need
else // we didn't time out:
// get the result of the method call here
alOut = deleg.EndInvoke(ar);
The steps above are:
1) Create an instance of your delegate, pointing to the helper method that invokes the remote object.
2) Invoke using the built-in BeginInvoke method, passing the parameters, null for the callback, and a new object() in the place of the state parameter of the BeginInvoke method. All we are interested in here is the IAsyncResult return object of the BeginInvoke call, we do not need a callback method or a state object.
3) Check the AsyncResult object's AsyncWaitHandle, calling it's WaitOne method with the timeout overload. This waits until the method either returns or times out. The WaitOne method itself returns true if success, and false for a timeout.
4) Take the appropriate action. If timeout, you can send back an error result, send a SysLog message, log to your database, and / or send an email to somebody to "go fix it".
You don't have to do this with WebServices, you can do it with any method call. Since you are blocking on the AsyncResult with the overloaded WaitOne, you can put this arrangement into a web page method with no issues, because page processing is halted until you either get a return from the method or a timeout. There are additional Wait objects that you can use, I don't cover them here, but there is more information on MSDN if you care to invest in further study.
The downloadable Visual Studio 2005 solution has the complete Webservice and TestClass, as well as a console app to run it through the paces.