为什么这个AsyncCallback测试有时会失败

本文关键字:失败 测试 AsyncCallback 为什么 | 更新日期: 2023-09-27 18:11:05

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

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

为什么这个AsyncCallback测试有时会失败

在此模式中,回调在线程池线程上运行,您应该从回调中调用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之前调用回调。正确的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);