【发布时间】:2014-10-28 12:27:49
【问题描述】:
我正在尝试使用 DirectorySearcher 获取 AD 用户列表:
using (var entry = new DirectoryEntry("LDAP://mydomain.com"))
using (var search = new DirectorySearcher(entry))
{
// Setup the query...
search.PageSize = 1000;
using (SearchResultCollection results = search.FindAll())
{
foreach (SearchResult result in results)
{
// Read the results and insert in a list
}
}
}
此查询最多可能需要一分钟,因此我正在线程池 (ThreadPool.QueueUserWorkItem) 中的一个线程中运行。查询工作正常,我得到了正确的结果。
但是,如果我在查询运行时关闭应用程序,我会系统地得到这个 MDA:RaceOnRCWCleanup was detected。确实,我的线程仍在运行,正在等待枚举继续:
[Managed to Native Transition]
System.DirectoryServices.dll!System.DirectoryServices.SearchResultCollection.ResultsEnumerator.MoveNext() + 0x4a bytes
MyApp.exe!MyApp.ActiveDirectory.FetchAllUsers() Line 125 + 0x4dd bytes
同时,终结器线程正在终结DirectoryEntry的一个实例:
mscorlib.dll!System.__ComObject.ReleaseSelf() + 0x5 bytes
mscorlib.dll!System.Runtime.InteropServices.Marshal.ReleaseComObject(object o) + 0x84 bytes
System.DirectoryServices.dll!System.DirectoryServices.DirectoryEntry.Unbind() + 0x27 bytes
System.DirectoryServices.dll!System.DirectoryServices.DirectoryEntry.Dispose(bool disposing) + 0x29 bytes
System.dll!System.ComponentModel.Component.Finalize() + 0x1b bytes
我可以验证(通过 Visual Studio 中的 Make Object ID)正在完成的 DirectoryEntry 是 SearchResultCollection 使用的内部实例。
- 为什么这个
DirectoryEntry在被使用的时候就已经定稿了? - 终结器是否知道这是一个将要退出的后台线程,并且是否将只能从该线程访问的对象视为可终结的?
- 我能做点什么吗?在退出应用程序之前等待查询完成是不可接受的。
更新 1
尝试在foreach 之后添加GC.KeepAlive(results)。还尝试通过首先通过反射在results 中的内部DirectoryEntry 上执行GC.KeepAlive()。不走运:条目仍在最终确定中...
using (var entry = new DirectoryEntry("LDAP://mydomain.com"))
using (var search = new DirectorySearcher(entry))
{
using (SearchResultCollection results = search.FindAll())
{
var rootEntryField = typeof(SearchResultCollection).GetField("rootEntry", BindingFlags.NonPublic | BindingFlags.Instance);
var rootEntry = rootEntryField.GetValue(results);
foreach (SearchResult result in results) // Callstack is here when entry is finalized
{
// Do something
}
GC.KeepAlive(results);
GC.KeepAlive(rootEntry); // This is the entry being finalized
}
GC.KeepAlive(search);
GC.KeepAlive(entry);
}
我注意到一个奇怪的细节是,有时,当我点击 MDA 时,SearchResultCollection 也已经被释放了。我认为它是通过最终确定处理的,因为我显然还没有调用Dispose()。在这种情况下,在我看来,GC 无法完成对象,因为我显然需要稍后处理它...
更新 2
我做了一个简单的测试,以确定当应用程序关闭而后台线程似乎仍在运行时,GC 是否可以从后台线程收集/完成对象。考虑这段代码在后台线程中运行:
using (new MyFinalizableType())
{
System.Threading.Thread.Sleep(100000);
}
如果您在 using 块中关闭应用程序,您可以验证~MyFinalizableType() 在线程仍处于活动状态时被调用(您可以在“线程”窗口中看到它及其调用堆栈) .
因此,总而言之,似乎GC 确实认为这些对象是可收集的,在这种情况下,没有什么可以阻止RaceOnRCWCleanup MDA。
请注意,您可以GC.SuppressFinalize 有问题的对象,但是 COM 引用会泄漏,因为永远不会在对象上调用 Dispose() 方法。与内核对象/句柄不同,当进程退出时,无法回收 AFAIK COM 引用。
【问题讨论】:
-
嗯,这是一个错误。由 DirectoryEntry.Unbind() 引起,它调用 Marshal.ReleaseComObject()。通常非常顽皮,当终结器调用 Unbind() 并且为已经终结的 COM 对象调用 ReleaseComObject 时,它会成为一个严重的错误。您无法修复错误,这是框架代码。 MDA 猜对并且您的程序将因 AccessViolationException 崩溃的可能性并不高,这发生在程序关闭时。因此,要么通过关闭 MDA 来忽略它,要么通过确保线程在退出之前结束来感觉更好。
-
我想我坚持忽略 MDA.. 但这仍然不能解释为什么对象会首先完成。
标签: c# active-directory garbage-collection