2011-10-13 165 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

好的,我已確認。我會擴大答案來解釋。 –

相關問題