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

发布于 2024-12-09 10:27:54 字数 1248 浏览 0 评论 0原文

我有以下类尝试充当简单的异步操作:

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

I have the following class which tries to act as a simple asynchronous operation :

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

The following unit test is intermittently failing, but I don't understand where the race condition is:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

萌化 2024-12-16 10:27:54

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

In this pattern, the callback is run on a thread pool thread and you are supposed to call EndInvoke from within the callback.

EndInvoke doesn't wait for the callback to complete ( because this would cause a deadlock ), so you have a race between the callback and your test method.


EDIT: The wait handle can be set before the callback has completed too. Try this:

[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 );
}
祁梦 2024-12-16 10:27:54

如前所述,在测试成功的情况下,您只是幸运,线程以这样的方式交错,即在对 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);

As already noted, on the occasions where the test succeeds you're just being lucky that the threads are interleaving in such a way that the callback gets called before the call to EndInvoke occurs. The proper APM pattern is to call your EndWriteLine within the callback which means that you have to pass the AsyncLineWriter as part of the state to the BeginInvoke method.

EDIT: there's an extra complication since the callback can occur after the IAsyncResult WaitHandle is signalled. So it's not that the callback doesn't get called it's just getting called after the check occurs. This fixes it:

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文