2011-10-13 166 views
4

我有以下类试图充当简单异步操作:为什么这个AsyncCallback测试在某些时候会失败?

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

看起来回调不是EndInvoke正在等待结束的异步操作的一部分。 – dtb

+0

为什么测试通过一段时间呢? –

+0

因为有时这两个线程以这种方式交错。 – dtb

回答

3

在这个模式中,回调是在一个线程池线程运行,你应该叫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); 
} 
+0

有趣的是,可以在回调完成之前设置AsyncWaitHandle。这工作。 –

3

正如已经指出的那样,在测试成功的情况下,您只是很幸运,线程交错的方式使得在调用EndInvoke之前调用回调函数。正确的APM模式是在回调中调用您的EndWriteLine,这意味着您必须将AsyncLineWriter作为状态的一部分传递给BeginInvoke方法。

编辑:有一个额外的复杂性,因为可能会出现回调IAsyncResultWaitHandle发出信号。所以并不是说回调没有被调用,它只是在检查发生后才被调用。这修复它:

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); 
+0

+1如果'r'是一个'AsyncResult',你也可以使用'AsyncResult.AsyncDelegate'。 –

+0

好吧,看起来很有希望,但这有时还是失败。如果你不相信我,试试吧。 –

+0

好的,我已确认。我会扩大答案来解释。 –

相关问题