c#线程异步问题

时间:2010-10-14 20:00:18

标签: c#

我有一些代码,当被调用时调用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));

4 个答案:

答案 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

这不是那个想要的。发生这种情况是因为内部开始/结束调用以这种方式工作:

  1. 执行代理
  2. Signal WaitHandle
  3. 执行回叫
  4. 由于这种情况发生在其他线程上,因此可能(很可能)线程切换发生在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”时,执行输出。

它可能不是整洁/优雅,但它是直接的,并且异步调用就是你想要的。