我有一些代码,当被调用时调用webservice,查询数据库并从本地缓存中获取值.然后它将这三个动作的返回值组合起来以产生结果.我没有按顺序执行这些操作,而是想并行异步执行它们.这是一些虚拟/示例代码:
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));
问题是最后一行抛出一个错误,因为dbResult在执行时仍然为null.一旦调用queryDB.EndInvoke,就会发信号通知WaitHandle,并且在将queryDB.EndInvoke的结果分配给dbResult之前继续执行.这周围有一个整洁/优雅的方式吗?
注意:我应该补充说这会影响dbResult,因为queryDB是要发信号的最后一个等待句柄.
更新:虽然我接受了菲利普的答案,但是在安德烈的评论之后,我应该补充说这也有效:
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,cacheResult));
解决方法
不幸的是,在EndInvoke()调用返回之前,始终会发出WaitHandle信号.这意味着你不能依赖于此.
如果你不能使用4.0,那么线程或手动等待处理系统可能会有序(或者可怕的Sleep()hack!).您还可以使用Invoked方法设置结果(因此在设置结果值后会发生EndInvoke),但这意味着将结果移动到共享位置,而不是局部变量 – 可能需要进行小的重新设计.
或者,如果你可以使用4.0,我会 – System.Threading.Tasks充满了’很棒的东西.你可以重写为:
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,cacheResult));