2014-03-07 60 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。