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));
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: