NUnit异步测试导致AppDomainUnloadedException

时间:2014-03-07 13:31:38

标签: wcf nunit async-await

我有一个带有异步操作的.NET 4.5 WCF服务。我有集成测试,它使用NetNamedPipeBinding构建服务主机并通过客户端点击操作。

但是,像这样的每个测试总是会导致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

1 个答案:

答案 0 :(得分:3)

我遇到了同样的问题。看起来这个问题似乎是“轻松的”#34;完成端口线程(在ThreadPool中),WCF已使用它来处理异步IO。

当使用ServiceHost.Close()时,它会发出所有已完成工作的线程的信号,但它们不会立即消失,也就是说,它们可能比ServiceHost.Close()操作的结束时间更长。因此,"关闭"由于测试运行结束,程序与NUnit引发的实际AppDomain卸载竞争。

基本上,在Thread.Sleep(<a couple of seconds>)&#34;修复&#34;之后的简单ServiceHost.Close()这个: - )

经过多次在互联网上搜索后,我无法找到针对此问题的强大解决方案(对于类似问题的选择,并非所有问题都归因于相同原因,google&#34;单元测试appdomainunloadedexception&#34 ;),没有办法抑制这种警告本身。

我尝试了不同的绑定和传输(包括NullTransport),但无济于事。

最后,我采用了这个&#34;解决方案&#34;:

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也是如此。有时候我会得到一个&#34;超时&#34;,但大部分时间都有效。

我确保为每个测试程序集调用一次此代码(即通过引用类型的静态ctor)。

像往常一样在YMMV。