我有一个带有异步操作的.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
答案 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。