简体   繁体   English

c#线程异步问题

[英]c# threading async problem

I have some code that when called calls a webservice, queries a database and fetches a value from local cache. 我有一些代码,当被调用时调用webservice,查询数据库并从本地缓存中获取值。 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. 问题是最后一行抛出一个错误,因为dbResult在执行时仍然为null。 As soon as queryDB.EndInvoke is called the WaitHandle is signalled and execution continues BEFORE the result of queryDB.EndInvoke is assigned to dbResult. 一旦调用queryDB.EndInvoke,就会发信号通知WaitHandle,并且在将queryDB.EndInvoke的结果分配给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. 注意:我应该补充一点,这只会影响dbResult,因为queryDB是要发出信号的最后一个等待句柄。

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. 不幸的是,WaitHandle将始终在EndInvoke()调用返回之前发出信号。 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!). 如果你不能使用4.0,那么线程或手动等待的系统可能会有序(或者可怕的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. 您也可以使用Invoked方法设置结果(因此设置结果值会发生EndInvoke),但这意味着将结果移动到共享位置,而不是局部变量 - 可能需要进行小的重新设计。

Or If you can use 4.0, I would - System.Threading.Tasks is chock full 'o great stuff. 或者,如果你可以用4.0,我想- System.Threading.Tasks是充满了“O伟大的东西。 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));

I would go with 3 threads here and avoid Invoke() . 我会在这里使用3个线程并避免使用Invoke() To me, threads are more readable, and you can even put its code into a anonymous method inside the Thread.Start() . 对我来说,线程更具可读性,甚至可以将其代码放入Thread.Start()内部的匿名方法中。

After starting, you should .Join() all 3 threads here, and you'll be sure that your results are ready. 在开始之后,你应该在这里所有3个线程.Join() ,你将确保你的结果准备就绪。

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

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 信号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. 因为这发生在线程上,所以主要的是(很可能)线程切换发生在2和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. 结果将是正确和确定的。

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. 然后当每个事件返回时更新状态,当所有三个都为“true”时执行输出。

It might not be neat/elegant, but it is straight forward and with asynchronous calls that's what you want. 它可能不是整洁/优雅,但它是直接的,并且异步调用就是你想要的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM