views:

455

answers:

4

I'm writing an ASP.NET web service using C# that has a DoLookup() function. For each call to the DoLookup() function I need my code to execute two separate queries: one to another web service at a remote site and one to a local database. Both queries have to complete before I can compile the results and return them as the response to the DoLookup method. The problem I'm dealing with is that I want to make this as efficient as possible, both in terms of response time and resource usage on the web server. We are expecting up to several thousand queries per hour. Here's a rough C#-like overview of what I have so far:

public class SomeService : System.Web.Services.WebService
{
    public SomeResponse DoLookup()
    {
     // Do the lookup at the remote web service and get the response 
     WebResponse wr = RemoteProvider.DoRemoteLookup(); 

     // Do the lookup at the local database and get the response
     DBResponse dbr = DoDatabaseLookup();

     SomeResponse resp = new SomeResponse( wr, dbr);

     return resp;
    }
}

The above code does everything sequentially and works great but now I want to make it more scalable. I know that I can call the DoRemoteLookup() function asynchronously ( RemoteProvider has BeginRemoteLookup / EndRemoteLookup methods) and that I can also do the database lookup asynchronously using the BeginExecuteNonQuery / EndExecuteNonQuery methods.

My question (finally) is this: how do I fire both the remote web service lookup AND the database lookup simultaneously on separate threads and ensure that they have both completed before returning the response?

The reason I want to execute both requests on separate threads is that they both potentially have long response times (1 or 2 seconds) and I'd like to free up the resources of the web server to handle other requests while it is waiting for responses. One additional note - I do have the remote web service lookup running asynchronously currently, I just didn't want to make the sample above too confusing. What I'm struggling with is getting both the remote service lookup AND the database lookup started at the same time and figuring out when they have BOTH completed.

Thanks for any suggestions.

A: 

Assuming you can have a callback function for both the web request and the database lookup then something along these lines may work

bool webLookupDone = false;
bool databaseLookupDone = false;

private void FinishedDBLookupCallBack()
{
    databaseLookupDone = true;
    if(webLookupDone)
    {
        FinishMethod();
    }
}

private void FinishedWebLookupCallBack()
{
    webLookupDone = true;
    if(databaseLookupDone)
    {
        FinishMethod();
    }
}
Bela
This code has a race condition, if both threads simultaneously set the done flag to true before checking if the other thread finished, FinishMethod will be called twice.
DSO
That thought had crossed my mind, but I wasn't sure how to prevent it. I'm not too familiar with multithreading.
Bela
Not hard to fix, just put a lock around each method, so that the setting of the done flag and the check for the other done flag is done atomically.
DSO
+2  A: 

You can use a pair of AutoResetEvents, one for each thread. At the end of thread execution, you call AutoResetEvents.Set() to trigger the event.

After spawning the threads, you use WaitAll() with the two AutoResetEvents. This will cause the thread to block until both events are set.

The caveat to this approach is that you must ensure the Set() is guarantee to be called, otherwise you will block forever. Additionally ensure that with threads you exercise proper exception handling, or you will inadvertently cause more performance issues when unhanded exceptions cause your web application to restart.

MSDN Has sample code regarding AutoResetEvent usage.

Alan
To ensure you don't block forever use the overload of WaitAll() which takes a timeout parameter (specifying an acceptable timeout for the operations to complete in). You can then check the return value of WaitAll() to determine whether it returned because all handles were signalled or because the timeout expired.
Simon Fox
Great point. I had assumed no timeout, because he wanted to ensure that both had completed prior to returning.
Alan
Thanks for the info Alan and Simon. I'll give this a try tomorrow.
TLiebe
+1  A: 

See Asynchronous XML Web Service Methods, How to: Create Asynchronous Web Service Methods and How to: Chain Asynchronous Calls with a Web Service Method.

But note the first paragraph of those articles:

This topic is specific to a legacy technology. XML Web services and XML Web service clients should now be created using Windows Communication Foundation (WCF).


BTW, doing things the way these articles say is important because it frees up the ASP.NET worker thread while the long-running task runs. Otherwise, you might be blocking the worker thread, preventing it from servicing further requests, and impacting scalability.

John Saunders
In this case, the calls need to execute simultaneously. The WebsService call itself isn't asynch--it must complete the lookup before returning the result to the caller.
Alan
I don't see why you couldn't get these techniques to work. If you consider your two async methods to be a single, compound async method, then you should be able to use http://msdn.microsoft.com/en-us/library/bb559019.aspx
John Saunders
Thanks for the links John. I do plan on making the DoLookup call asynchronous once it get it calling it's internal methods asynchronously. I'll have a look at the links you provided.
TLiebe
A: 

I guess I don't have enough rep to upvote nor to comment. So this is a comment on John Saunders answer and Alan's comment on it.

You definitely want to go with John's answer if you are concerned about scalability and resource consumption.

There are two considerations here: Speeding up an individual request, and making your system handle many concurrent requests efficiently. The former both Alan's and John's answer achieve by performing the external calls in parallel.

The latter, and it sounds like that was your main concern, is achieved by not having threads blocked anywhere, i.e. John's answer.

Don't spawn your own threads. Threads are expensive, and there are already plenty of threads in the IO Threadpool that will handle your external calls for you if you use the asynch methods provided by the .net framework.

Your service's webmethod needs to be asynch as well. Otherwise a worker thread will be blocked until your external calls are done (it's still 1-2 seconds even if they run in parallel). And you only have 12 threads per CPU handling incoming requests (if your machine.config is set according to recommendation.) I.e. you would at most be able to handle 12 concurrent requests (times the # of CPUs). On the other hand if your web method is asynch the Begin will return pretty much instantenously and the thread returned to the worker thread pool ready to handle another incoming request, while your external calls are being waited on by the IO completion port, where they will be handled by threads from the IO thread pool, once they return.

turnhose
Thanks turnhose. I am planning on having a BeginLookup() and EndLookup() event once I get everything else working. I did make a Begin/End Lookup() version for sequential calls to the remote web service and database - now I just have to make an asynch version that in turn calls it's internal calls asynchronously.
TLiebe