【问题标题】:Objects initialized in constructors are not collected by GC in the same way that objects initialized in methods?在构造函数中初始化的对象不会像在方法中初始化的对象那样被 GC 收集?
【发布时间】:2014-07-23 18:03:13
【问题描述】:

我的示例是在托管代码中使用非托管代码。

如果我在一个方法中实例化一个类级别的字段,GC 会在我的类的实例被收集之前收集这个字段,如下所示:

public class SpyAgent
{
   public delegate int WindowProcedureDelegate(int Wnd, uint Msg, int WParam, int LParam);

   private WindowProcedureDelegate _windowProcedure;

   public void SpyAgent()
   {
   }

   ...

   public void SomeRandomMethod()
   {
      ...

      //GC will collect this instance before SpyAgent dispose.
      _windowProcedure = new WindowProcedureDelegate(WindowProcedure);

      SetWindowLong(window.Handle, (IntPtr)_windowProcedureIndex, _windowProcedure);
   }

   [DllImport("user32.dll")]
   private static extern IntPtr SetWindowLong(IntPtr hWnd, IntPtr nIndex, WindowProcedureDelegate newProc);

   ...
}

使用系统一分钟后,我收到以下错误:

检测到托管调试助手“CallbackOnCollectedDelegate” 一个问题 'C:\Acaz\Supervise.Bootstrap\Supervise.Bootstrap\Supervise.Bootstrap\Supervise.Bootstrap\Contoso\bin\Debug\Contoso.vshost.exe'。

附加信息:对类型为垃圾收集的委托进行了回调 'Supervise.Application!Supervise.Application.SpyAgent+WindowProcedureDelegate::Invoke'。 这可能会导致应用程序崩溃、损坏和数据丢失。什么时候 将委托传递给非托管代码,它们必须由 托管应用程序,直到保证它们永远不会 调用。

但是当我在构造函数中实例化委托时,我没有遇到更多错误。

public class SpyAgent
{
   public delegate int WindowProcedureDelegate(int Wnd, uint Msg, int WParam, int LParam);

   private WindowProcedureDelegate _windowProcedure;

   public void SpyAgent()
   {
      //This instance will be collected by GC just when SpyAgent start to being collected too.
      _windowProcedure = new WindowProcedureDelegate(WindowProcedure);
   }

   ...

   public void SomeRandomMethod()
   {
      ...

      SetWindowLong(window.Handle, (IntPtr)_windowProcedureIndex, _windowProcedure);
   }

   [DllImport("user32.dll")]
   private static extern IntPtr SetWindowLong(IntPtr hWnd, IntPtr nIndex, WindowProcedureDelegate newProc);

   ...
}

为什么会有这种行为?为什么在收集类之前收集在方法中初始化的类级别的字段。没有意义,因为该字段是在类级别声明的,可以用于整个类。我将引用存储在类级项目中。

为什么当我在构造函数中初始化字段时,直到类也开始被收集时才收集字段?

垃圾收集器的行为与在方法和构造函数中初始化的对象有什么区别?

【问题讨论】:

  • 您需要告诉我们您如何验证您所描述的事情确实发生了,因为那不是应该发生的。我认为您更有可能遇到不同的错误/问题,让您认为是这种情况。请告诉我们您做了什么来验证这一点。如果可能,您能否创建一个小而完整的程序,可以编译并运行来验证这一点?
  • 说清楚,没有区别。假设您调用了实例化对象并将其存储到字段中的方法,它应该具有与在构造函数中分配和分配它的另一个示例完全相同的生命周期。
  • 好的。我会将问题编辑为更完整的问题。
  • 你能告诉我们你构造对象的代码吗? IE。间谍代理。
  • 好吧,问题不在于对象被垃圾收集得太快,因为与构造函数相比,将引用从方法存储到字段中没有区别。一旦引用在现场,两种情况下的生命周期都是相同的。我认为您将其提供给 Win32 函数的方式更有可能存在问题。也许 .NET 没有正确包装它?或者您尝试执行的指定操作可能无法通过 C# 完成?

标签: c# .net memory-management garbage-collection


【解决方案1】:

代码 sn-ps 似乎与您遇到问题的真实代码的匹配度很差。但核心问题是本机代码依赖于委托对象。垃圾收集器可以看到该依赖关系,它对 GetWindowLong() 一无所知。这使得它很可能过早收集委托对象,最常见的是在下一次收集期间。当 Windows 传递消息时,这将导致严重崩溃,当您从调试器运行时,托管调试器助手会在它产生无法诊断的响亮砰砰声之前告诉您。

与 .NET Framework 一样,您不应自己编写此代码。子类化窗口是一种非常普遍的需求,Winforms 和 WPF 也需要这样做。与其从 scatch 中创建自己的类,不如从 NativeWindow class 派生出自己的类。覆盖WndProc() 方法以实现您自己的消息处理。当您准备好对窗口进行子类化时,调用它的AssignHandle() 方法。当您看到 WM_NCDESTROY 消息时,在您的 WndProc 覆盖中调用ReleaseHandle(),这是在窗口到期之前发送到窗口的最后一条消息。 NativeWindow 负责代理管道。例如:

class SpyAgent : NativeWindow {
    public SpyWindow(IntPtr hWnd) {
        this.AssignHandle(hWnd);
    }
    protected override void WndProc(ref Message m) {
        if (m.Msg == 130) this.ReleaseHandle();
        // Your code here
        //...
        base.WndProc(ref m);
    }
}

【讨论】:

  • 我无法控制将被子类化的应用程序。
  • 这无关紧要,您只需要一个 IntPtr,即您传递给构造函数的那个​​。当然你已经有了一个,因为你正在调用 SetWindowLong()。
  • 我必须控制主窗口中的所有窗口,如果使用主窗口句柄,创建我的本机实例,分配句柄,找到该窗口的所有子窗口并执行相同操作,创建我的本机窗口对象并分配句柄。你认为这样做可以吗?
  • 您需要再次单击该按钮才能提出另一个问题。
【解决方案2】:

无法保证调用终结器的顺序。您的对象都因为无根且有资格同时进行垃圾收集。您应该阅读有关“复活”的 GC 概念,其中没有根但仍可从终结器队列中访问的对象再次获得根(通过将其引用提供给根对象)。

在实践中,构造顺序和内存顺序可能影响完成顺序,但这没有记录在案。不要依赖它,因为它可以在 .NET 版本之间更改。

【讨论】:

    【解决方案3】:

    我发现了问题。

    是因为我不止一次调用 SomeRandom 方法。每次我对该字段进行新的委托归属时,我都会丢失过去的引用,然后 GC 会收集这个过去的实例,因为没有在任何地方引用。 :)

    【讨论】:

      猜你喜欢
      • 2021-11-06
      • 1970-01-01
      • 1970-01-01
      • 2021-09-10
      • 2012-08-14
      • 2012-05-14
      • 2012-03-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多