我也有同样的问题。看起来问题似乎出在 WCF 用来处理异步 IO 的“宽松”完成端口线程(在线程池中)。
When ServiceHost.Close()
使用时,它将向所有那些线程发出工作已完成的信号,但它们不会立即消失,也就是说,它们可能比线程结束还存活。ServiceHost.Close()
手术。因此,由于测试运行结束,“关闭”过程与 NUnit 引起的实际 AppDomain 卸载发生竞争。
基本上,一个简单的Thread.Sleep(<a couple of seconds>)
之后ServiceHost.Close()
“修复”这个:-)
在互联网上进行了大量搜索之后,我找不到针对此问题的可靠解决方案(对于一系列类似的问题,但并非全部都是由于相同的原因,谷歌“单元测试appdomainunloadedexception”),缺乏某种方法来抑制这个警告本身。
我尝试了不同的绑定和传输(包括空传输 http://www.codeproject.com/Articles/20709/NullTransport-for-WCF),但没有效果。
最后我选择了这个“解决方案”:
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 秒的超时是完全任意的,每次重试之间的 5 毫秒的等待也是任意的。有时我确实会遇到“超时”,但大多数时候它都有效。
我确保每个测试程序集都调用此代码一次(即通过引用类型的静态构造函数)。
像往常一样,在这种情况下YMMV。