我有一些代码,当被调用时调用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, wsResult, cacheResult));
答案 0 :(得分:3)
不幸的是,WaitHandle将始终在EndInvoke()
调用返回之前发出信号。这意味着你不能依赖这个。
如果你不能使用4.0,那么线程或手动等待处理系统可能会有序(或者可怕的Sleep()hack!)。您也可以使用Invoked方法设置结果(因此在设置结果值后,EndInvoke发生),但这意味着将结果移动到共享位置,而不是局部变量 - 可能需要重新设计。
或如果你可以使用4.0,我会 - System.Threading.Tasks充满了o很棒的东西。你可以改写为:
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));
答案 1 :(得分:1)
我会在这里使用3个主题并避免使用Invoke()
。对我而言,线程更具可读性,您甚至可以将其代码放入Thread.Start()
内的匿名方法。
启动后,您应该.Join()
所有3个主题,并确保您的结果已准备就绪。
这将是:
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();
答案 2 :(得分:1)
首先,我将解释它为什么会发生,然后告诉如何解决它。
让我们编写简单的程序:
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);
输出是:
1 at 3
3 at 1
Res1 0
2 at 3
Res2 5
这不是那个想要的。发生这种情况是因为内部开始/结束调用以这种方式工作:
由于这种情况发生在其他线程上,因此可能(很可能)线程切换发生在2到3之间。
要修复它,你应该这样做:
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);
结果将是正确的和确定的。
答案 3 :(得分:0)
我很想把查询放到三个可以异步调用的方法中,并在完成后触发“完成”事件。然后当每个事件返回时更新状态,当所有三个都为“true”时,执行输出。
它可能不是整洁/优雅,但它是直接的,并且异步调用就是你想要的。