C# 为什么这个异步回调测试有时会失败?

C# 为什么这个异步回调测试有时会失败?,c#,asynchronous,race-condition,C#,Asynchronous,Race Condition,我有一个试图充当简单异步操作的类: public class AsyncLineWriter { private delegate void SynchronousWriteLineDelegate(string message); private SynchronousWriteLineDelegate DoWriteLine; private void SynchronousWriteLine(string message) { Console

我有一个试图充当简单异步操作的类:

public class AsyncLineWriter
{
    private delegate void SynchronousWriteLineDelegate(string message);
    private SynchronousWriteLineDelegate DoWriteLine;
    private void SynchronousWriteLine(string message)
    {
        Console.WriteLine(message);
    }
    public AsyncLineWriter()
    {
        DoWriteLine = new SynchronousWriteLineDelegate(SynchronousWriteLine);

    public IAsyncResult BeginWriteLine(string message, AsyncCallback callback, object state)
    {
        return DoWriteLine.BeginInvoke(message,callback,state);
    }
    public void EndWriteLine(IAsyncResult asyncResult)
    {
        DoWriteLine.EndInvoke(asyncResult);
    }
}
以下单元测试间歇性失败,但我不明白竞争条件在哪里:

[TestMethod]
public void Callback_is_called()
{
    // Arrange
    AsyncLineWriter lineWriter = new AsyncLineWriter();
    object state = new object();
    object callbackState = null;
    AsyncCallback callback = (r) =>
        {
            callbackState = r.AsyncState;
        };

    // Act
    IAsyncResult asyncResult = lineWriter.BeginWriteLine("test", callback, state);
    lineWriter.EndWriteLine(asyncResult);

    // Assert
    Assert.AreSame(state, callbackState);
}

如前所述,在测试成功的情况下,线程以这样的方式交错,在调用
EndInvoke
之前调用回调,这是幸运的。正确的APM模式是在回调中调用EndWriteLine,这意味着您必须将
AsyncLineWriter
作为状态的一部分传递给
BeginInvoke
方法

编辑:有一个额外的复杂性,因为回调可能发生在发出信号的
IAsyncResult
WaitHandle
之后。所以并不是回调没有被调用,而是在检查发生后被调用。这就解决了这个问题:

AsyncLineWriter lineWriter = new AsyncLineWriter();
Object myState = new Object();
object[] state = new object[2];
state[0] = lineWriter;
state[1] = myState;
object callbackState = null;

ManualResetEvent evnt = new ManualResetEvent(false);

AsyncCallback callback = (r) =>
    {  
        Object[] arr = (Object[])r.AsyncState;
        LineWriter lw = (LineWriter)arr[0];
        Object st = arr[1];
        callbackState = st;
        lw.EndWriteLine(r);
        evnt.Set();
    };

// Act
IAsyncResult asyncResult = lineWriter.BeginWriteLine("test", callback, state);

//asyncResult.AsyncWaitHandle.WaitOne(); -- callback can still happen after this!

evnt.WaitOne();

//Assert
Assert.AreSame(myState, callbackState);

在此模式中,回调在线程池线程上运行,您应该从回调中调用
EndInvoke

EndInvoke
不会等待回调完成(因为这会导致死锁),因此回调和测试方法之间存在竞争


编辑:也可以在回调完成之前设置等待句柄。试试这个:

[TestMethod]
public void Callback_is_called()
{
    // Arrange
    var lw = new AsyncLineWriter();

    object state = new object();
    object callbackState = null;

    var mre = new ManualResetEvent( false );

    AsyncCallback callback = r =>
        {
            callbackState = r.AsyncState;

            lw.EndWriteLine( r );

            mre.Set();
        };

    // Act
    var ar = lw.BeginWriteLine( "test", callback, state );
    mre.WaitOne();

    // Assert
    Assert.AreSame( state, callbackState );
}

回调似乎不是EndInvoke等待结束的异步操作的一部分。那么,为什么测试要通过一段时间呢?因为有时两个线程以这种方式交错。嗯?如果您可以提供一个修复程序,使此测试通过并且仍然具有所需的行为(证明调用了回调),那么这将是一个很好的答案。+1如果
r
AsyncResult
,您也可以使用
AsyncResult.AsyncDelegate
。好的,看起来很有希望,但有时仍然失败。如果你不相信我就试试。好的,我已经证实了。我将展开答案进行解释。有趣的是,可以在回调完成之前设置AsyncWaitHandle。这很有效。