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。这很有效。