2014-03-07 54 views
6

我有一個帶有異步操作的.NET 4.5 WCF服務。我有集成測試,它使用NetNamedPipeBinding構造服務主機並通過客戶端訪問操作。NUnit異步測試導致AppDomainUnloadedException

然而,這樣每個測試總是引起NUnit的報告如下:

System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain. 
This can happen if the test(s) started a thread but did not stop it. 
Make sure that all the threads started by the test(s) are stopped before completion. 

一切正常給我。任何人都可以看到可能造成這種情況?我在GitHub上有一個完整的代碼示例:https://github.com/devlife/codesamples

+0

您使用的是哪個版本的NUnit?老版本的課程將無法使用。 http://www.anthonysteele.co.uk/async-and-await-with-nunit –

+0

@LexLi這是指將NUnit測試標記爲異步,而不是調用異步WCF操作。這些只是返回任務的方法,您可以輕鬆調用'.Wait()'或'.Result'。 –

+0

@PanagiotisKanavos我希望你在GitHub上檢查他的代碼。 –

回答

3

我遇到同樣的問題。看起來問題是WCF用來處理異步IO的「寬鬆」完成端口線程(在ThreadPool中)。

當使用ServiceHost.Close()時,它會發信號通知所有已完成工作的線程,但它們不會立即消失,也就是說,它們可能會超過ServiceHost.Close()操作的結束時間。因此,由於測試運行結束,「關閉」過程與由NUnit誘發的實際AppDomain卸載競爭。

基本上,一個簡單的Thread.Sleep(<a couple of seconds>)ServiceHost.Close()「修復」這個:-)

後在互聯網上一番搜索周圍我無法找到針對此問題的強大的解決方案(對於選擇類似的問題,不所有由於同樣的原因,谷歌「單元測試appdomainunloadedexception」),缺乏一些方法來壓制這種警告本身。

我試過不同的綁定和傳輸(包括NullTransport),但無濟於事。

在我這個「解決方案」結算結束:

static void PreventPrematureAppDomainUnloadHack() 
{ 
    // 
    // When NUnit unloads the test AppDomain, the WCF started IO completion port threads might 
    // not have exited yet. 
    // That leads to AppDomainUnloadedExceptions being raised after all is said and done. 
    // While native NUnit, ReSharper oder TestDriven.NET runners don't show these, VSTest (and 
    // TFS-Build) does. Resulting in very annoying noise in the form of build/test warnings. 
    // 
    // The following code _attempts_ to wait for all completion port threads to end. This is not 
    // an exact thing one can do, however we mitigate the risk of going wrong by several factors: 
    // (1) This code is only used during Unit-Tests and not for production code. 
    // (2) It is only called when the AppDomain in question is about to go away anway. 
    //  So the risk of someone starting new IO threads while we're waiting is very 
    //  low. 
    // (3) Finally, we have a timeout in place so that we don't wait forever if something 
    //  goes wrong. 
    // 
    if (AppDomain.CurrentDomain.FriendlyName.StartsWith("test-domain-", StringComparison.Ordinal)) 
    { 
     Console.WriteLine("AppDomainUnloadHack: enabled (use DbgView.exe for details)."); 
     Trace.WriteLine(string.Format("AppDomainUnloadHack: enabled for domain '{0}'.", AppDomain.CurrentDomain.FriendlyName)); 

     AppDomain.CurrentDomain.DomainUnload += (sender, args) => 
     { 
      int activeIo; 
      var sw = Stopwatch.StartNew(); 
      var timeout = TimeSpan.FromSeconds(3); 

      do 
      { 
       if (sw.Elapsed > timeout) 
       { 
        Trace.WriteLine("AppDomainUnloadHack: timeout waiting for threads to complete."); 
        sw.Stop(); 
        break; 
       } 

       Thread.Sleep(5); 

       int maxWorkers; 
       int availWorkers; 
       int maxIo; 
       int availIo; 
       ThreadPool.GetMaxThreads(out maxWorkers, out maxIo); 
       ThreadPool.GetAvailableThreads(out availWorkers, out availIo); 
       activeIo = maxIo - availIo; 

       Trace.WriteLine(string.Format("AppDomainUnloadHack: active completion port threads: {0}", activeIo)); 

      } while (activeIo > 0); 

      Trace.WriteLine(string.Format("AppDomainUnloadHack: complete after {0}", sw.Elapsed)); 
     }; 
    } 
} 

3秒的超時時間是完全任意的,所以是爲5ms每次重試之間的等待。有時候我會得到一個「超時」,但大部分時間都是有效的。

我確保每個測試程序集(即通過引用類型的靜態ctor)調用此代碼一次。

像往常一樣在這種情況下YMMV。