IAsyncResult.AsyncWaitHandle.WaitOne()在回调之前完成

Noe*_*ams 9 .net c# multithreading

这是代码:

class LongOp
{
    //The delegate
    Action longOpDelegate = LongOp.DoLongOp;
    //The result
    string longOpResult = null;

    //The Main Method
    public string CallLongOp()
    {
        //Call the asynchronous operation
        IAsyncResult result = longOpDelegate.BeginInvoke(Callback, null);

        //Wait for it to complete
        result.AsyncWaitHandle.WaitOne();

        //return result saved in Callback
        return longOpResult;
    }

    //The long operation
    static void DoLongOp()
    {
        Thread.Sleep(5000);
    }

    //The Callback
    void Callback(IAsyncResult result)
    {
        longOpResult = "Completed";
        this.longOpDelegate.EndInvoke(result);
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是测试用例:

[TestMethod]
public void TestBeginInvoke()
{
    var longOp = new LongOp();
    var result = longOp.CallLongOp();

    //This can fail
    Assert.IsNotNull(result);
}
Run Code Online (Sandbox Code Playgroud)

如果运行此测试用例可能会失败.为什么呢?

关于delegate.BeginInvoke如何工作的文档很少.有没有人有任何他们想分享的见解?

更新 这是一个微妙的竞争条件,在MSDN或其他地方没有详细记录.如接受的答案中所解释的那样,问题是当操作完成时,发出等待句柄的信号,然后执行回调.该信号释放等待的主线程,现在回调执行进入"竞争".Jeffry Richter的建议实现显示了幕后发生的事情:

  // If the event exists, set it   
  if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();

  // If a callback method was set, call it  
  if (m_AsyncCallback != null) m_AsyncCallback(this);
Run Code Online (Sandbox Code Playgroud)

有关解决方案,请参阅Ben Voigt的回答.该实现不会产生第二个等待句柄的额外开销.

Tor*_*och 10

异步操作完成时,将发出ASyncWaitHandle.WaitOne()信号.同时调用CallBack().

这意味着WaitOne()之后的代码在主线程中运行,而CallBack在另一个线程中运行(可能与运行DoLongOp()的线程相同).这会导致竞争条件,其中longOpResult的值在返回时基本上是未知的.

人们可能已经预料到,当CallBack完成时,ASyncWaitHandle.WaitOne()会发出信号,但这不是它的工作方式;-)

您需要另一个ManualResetEvent让主线程等待CallBack设置longOpResult.


Ben*_*igt 5

正如其他人所说,result.WaitOne只是意味着目标BeginInvoke已经完成,而不是回调.所以只需将后处理代码放入BeginInvoke委托中即可.

    //Call the asynchronous operation
    Action callAndProcess = delegate { longOpDelegate(); Callafter(); };
    IAsyncResult result = callAndProcess.BeginInvoke(r => callAndProcess.EndInvoke(r), null);


    //Wait for it to complete
    result.AsyncWaitHandle.WaitOne();

    //return result saved in Callafter
    return longOpResult;
Run Code Online (Sandbox Code Playgroud)