7

I have some code that when called calls a webservice, queries a database and fetches a value from local cache. It then combines the return values from these three actions to produce its result. Rather than perform these actions sequentially I want to perform them asynchronously in parallel. Here's some dummy/example code :

var waitHandles = new List<WaitHandle>();

var wsResult = 0;
Func<int> callWebService = CallWebService;
var wsAsyncResult = callWebService.BeginInvoke(res => { wsResult = callWebService.EndInvoke(res); }, null);
waitHandles.Add(wsAsyncResult.AsyncWaitHandle);

string dbResult = null;
Func<string> queryDB = QueryDB;
var dbAsyncResult = queryDB.BeginInvoke(res => { dbResult = queryDB.EndInvoke(res); }, null);
waitHandles.Add(dbAsyncResult.AsyncWaitHandle);

var cacheResult = "";
Func<string> queryLocalCache = QueryLocalCache;
var cacheAsyncResult = queryLocalCache.BeginInvoke(res => { cacheResult = queryLocalCache.EndInvoke(res); }, null);
waitHandles.Add(cacheAsyncResult.AsyncWaitHandle);

WaitHandle.WaitAll(waitHandles.ToArray());          
Console.WriteLine(string.Format(dbResult, wsResult, cacheResult));

The problem is that the last line throws an error because dbResult is still null when it gets executed. As soon as queryDB.EndInvoke is called the WaitHandle is signalled and execution continues BEFORE the result of queryDB.EndInvoke is assigned to dbResult. Is there a neat/elegant way round this ?

Note : I should add that this affects dbResult simply because the queryDB is the last wait handle to be signalled.

Update : While I accepted Philip's answer which is great, following Andrey's comments, I should add that this also works :

var waitHandles = new List<WaitHandle>();

var wsResult = 0;
Func<int> callWebService = CallWebService;
var wsAsyncResult = callWebService.BeginInvoke(null, null);
waitHandles.Add(wsAsyncResult.AsyncWaitHandle);

string dbResult = null;
Func<string> queryDB = QueryDB;
var dbAsyncResult = queryDB.BeginInvoke(null, null);
waitHandles.Add(dbAsyncResult.AsyncWaitHandle);

var cacheResult = "";
Func<string> queryLocalCache = QueryLocalCache;
var cacheAsyncResult = queryLocalCache.BeginInvoke(null, null);
waitHandles.Add(cacheAsyncResult.AsyncWaitHandle);

WaitHandle.WaitAll(waitHandles.ToArray());

var wsResult = callWebService.EndInvoke(wsAsyncResult);
var dbResult = queryDB.EndInvoke(dbAsyncResult);
var cacheResult = queryLocalCache.EndInvoke(cacheAsyncResult);

Console.WriteLine(string.Format(dbResult, wsResult, cacheResult));
1
  • Not an answer but an upgrade to Fx4 would make this so much easier. Commented Oct 14, 2010 at 20:31

4 Answers 4

3

Unfortunately, the WaitHandle will always be signaled before the EndInvoke() call returns. Which mean you can't rely on this.

If you cannot use 4.0, a system of threads or manual waithandles is probably going to be in order (or the dreaded Sleep() hack!). You can also have the Invoked method be what sets your results (so EndInvoke happens after the result value is set), but that means moving the results to a shared location, and not local variables - probably requiring a small redesign.

Or If you can use 4.0, I would - System.Threading.Tasks is chock full 'o great stuff. You could rewrite to:

var tasks = new List<Task>();

var wsResult = 0;
string dbResult = null;
var cacheResult = "";

tasks.Add( new Task( ()=> wsResult = CallWebService()));
tasks.Add( new Task( ()=> dbResult = QueryDB()));
tasks.Add( new Task( ()=> cacheResult = QueryLocalCache()));

tasks.ForEach( t=> t.Start());
Task.WaitAll( tasks.ToArray());

Console.WriteLine(string.Format(dbResult, wsResult, cacheResult));
Sign up to request clarification or add additional context in comments.

2 Comments

@Andrey always is correct; it will be signaled before the call returns - it has to be, as the method itself is what signals the wait handle so it cannot return before signaling. That does not mean that the waiting thread will receive control immediately, though.
right, sorry, i meant that thread switch is not determined. but signaling is :)
1

I would go with 3 threads here and avoid Invoke(). To me, threads are more readable, and you can even put its code into a anonymous method inside the Thread.Start().

After starting, you should .Join() all 3 threads here, and you'll be sure that your results are ready.

It would be something like:

Thread t1=new Thread( delegate() { wsResult = CallWebService(); } );
Thread t2=new Thread( delegate() { dbResult = QueryDb(); } );
Thread t3=new Thread( delegate() { cacheResult = QueryLocalCache(); } );
t1.Start(); t2.Start(); t2.Start();
t1.Join(); t2.Join(); t3.Join();

3 Comments

Maybe I've misunderstood but isn't that what BeginInvoke does ? It lauches a new thread. If I create my own threads I will still have to use find a mechanism for waiting for each to complete similar to waithandles right ?
Creating a new thread has a lot of unseen overhead, including allocating/reserving stack space for the new thread, etc. When you have very little to do in the background thread it can almost take more time to set up the thread than to execute it. ThreadPool eliminates this overhead by reusing the same threads for different pieces of work.
IMO; BeginInvoke() -> totally unreadable; anybody cares to post an example with the ThreadPool?
1

First i will explain why does it happen, then tell how to fix it.

Let's write simple program:

        var wsResult = 0;
        Func<int> callWebService = () => {
            Console.WriteLine("1 at " + Thread.CurrentThread.ManagedThreadId);
            return 5;
        };
        var wsAsyncResult = callWebService.BeginInvoke(res => {
            Console.WriteLine("2 at " + Thread.CurrentThread.ManagedThreadId);
            wsResult = callWebService.EndInvoke(res);
        }, null);
        wsAsyncResult.AsyncWaitHandle.WaitOne();
        Console.WriteLine("3 at " + Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine();
        Console.WriteLine("Res1 " + wsResult);
        Thread.Sleep(1000);
        Console.WriteLine("Res2 " + wsResult);

output is:

1 at 3
3 at 1

Res1 0
2 at 3
Res2 5

which is not that wanted. This happens because internally Begin/End Invoke works this way:

  1. Execute delegate
  2. Signal WaitHandle
  3. Execute callback

Since this happens on thread other then main it is possible (and very likely) that thread switch happens right between 2 and 3.

To fix it you should do:

        var wsResult = 0;
        Func<int> callWebService = () => {
            Console.WriteLine("1 at " + Thread.CurrentThread.ManagedThreadId);
            return 5;
        };
        var wsAsyncResult = callWebService.BeginInvoke(null, null);
        wsAsyncResult.AsyncWaitHandle.WaitOne();
        wsResult = callWebService.EndInvoke(wsAsyncResult);

and result will be both correct and deterministic.

Comments

0

I'd be tempted to put the queries into three methods that can be called asynchronously and fire a "complete" event when finished. Then when each event comes back update a status and when all three are "true" perform your output.

It might not be neat/elegant, but it is straight forward and with asynchronous calls that's what you want.

1 Comment

Thanks Chris. I had considered that but it feels so clunky and like I'm having to write the WaitHandle signal semaphore myself which seems to defeat the purpose of having them in the first place.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.