为什么这个对象在被使用时最终确定?

时间:2014-10-28 12:27:50

标签: c# active-directory garbage-collection

我正在尝试使用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)最终确定的DirectoryEntrySearchResultCollection使用的内部实例。

  • 为什么这个DirectoryEntry在被使用时最终确定?
  • 终结器是否知道这是一个后台线程要退出,是否认为只能从该线程到达的对象可以最终化?
  • 我可以为此做些什么吗?在退出应用程序之前等待查询完成是不可接受的。

更新1

尝试在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不能最终确定对象,因为我以后显然需要它来处理它......

更新2

我做了一个简单的测试,以确定当应用程序关闭时,GC是否可以从后台线程收集/完成对象,而线程似乎仍在运行。考虑在后台线程中运行的代码:

using (new MyFinalizableType())
{
    System.Threading.Thread.Sleep(100000);
}

如果你仍然在使用块中关闭应用程序,你可以验证线程仍处于活动状态时调用~MyFinalizableType() (你可以在线程中看到它和它的callstack)窗口)。

因此,总而言之,似乎 GC确实将这些对象视为可收集的,在这种情况下,没有什么可以阻止RaceOnRCWCleanup MDA

请注意,您可以GC.SuppressFinalize有问题的对象但是COM引用会泄漏,因为永远不会在对象上调用Dispose()方法。当进程退出时,与内核对象/句柄不同,无法回收AFAIK COM引用。

2 个答案:

答案 0 :(得分:0)

我不完全确定为什么DirectoryEntry对象不再被视为可达。我怀疑终结器以不同的方式处理后台线程中的变量。

我最好的猜测是因为它通过互操作COM包装过程以某种方式断开连接,即它在技术上可以到达,但只能通过对GC不透明的包装器。 / p>

您应该能够在GC.KeepAlive(entry);循环后添加foreach来解决竞争条件。这将确保存储在entry中的引用在该语句之前被认为是可访问的(否则,允许JIT编译器优化变量并使对象在之前无法访问)。

答案 1 :(得分:0)

只要对象永远不会被可执行代码访问,就可以最终确定对象。在最终确定对象的实例方法的执行过程中,可以满足该条件。如果从对象的唯一可访问引用调用该方法,则调用者在它超出范围之前不会再次访问该变量,并且该方法的执行将不会访问this变量(隐式或显式) )在方法完成之前,该对象有资格完成。 (除其他原因之外,这就是为什么处理最终确定很难的原因。)

代码在线程池/后台线程中的事实在这里并不相关。

Here是关于该主题的博客文章,供进一步阅读。