我正在尝试使用DirectorySearcher
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
在被使用时最终确定?尝试在GC.KeepAlive(results)
之后添加foreach
。还尝试通过首先通过反射在DirectoryEntry
的内部results
上执行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不能最终确定对象,因为我以后显然需要它来处理它......
我做了一个简单的测试,以确定当应用程序关闭时,GC是否可以从后台线程收集/完成对象,而线程似乎仍在运行。考虑在后台线程中运行的代码:
using (new MyFinalizableType())
{
System.Threading.Thread.Sleep(100000);
}
如果你仍然在使用块中关闭应用程序,你可以验证线程仍处于活动状态时调用~MyFinalizableType()
(你可以在线程中看到它和它的callstack)窗口)。
因此,总而言之,似乎 GC确实将这些对象视为可收集的,在这种情况下,没有什么可以阻止RaceOnRCWCleanup
MDA
请注意,您可以GC.SuppressFinalize
有问题的对象但是COM引用会泄漏,因为永远不会在对象上调用Dispose()
方法。当进程退出时,与内核对象/句柄不同,无法回收AFAIK COM引用。
答案 0 :(得分:0)
我不完全确定为什么DirectoryEntry
对象不再被视为可达。我怀疑终结器以不同的方式处理后台线程中的变量。
我最好的猜测是因为它通过互操作COM包装过程以某种方式断开连接,即它在技术上可以到达,但只能通过对GC不透明的包装器。 / p>
您应该能够在GC.KeepAlive(entry);
循环后添加foreach
来解决竞争条件。这将确保存储在entry
中的引用在该语句之前被认为是可访问的(否则,允许JIT编译器优化变量并使对象在之前无法访问)。
答案 1 :(得分:0)
只要对象永远不会被可执行代码访问,就可以最终确定对象。在最终确定对象的实例方法的执行过程中,可以满足该条件。如果从对象的唯一可访问引用调用该方法,则调用者在它超出范围之前不会再次访问该变量,并且该方法的执行将不会访问this
变量(隐式或显式) )在方法完成之前,该对象有资格完成。 (除其他原因之外,这就是为什么处理最终确定很难的原因。)
代码在线程池/后台线程中的事实在这里并不相关。
Here是关于该主题的博客文章,供进一步阅读。