3

我在 C# 中使用 Actions,我想知道一旦我希望 GC 正确收集对象,是否需要将 Action 的实例设置为 null?这是一个例子:

public class A
{
 public Action a;
}

public class B
{
  public string str;
}

public class C
{
 public void DoSomething()
 {
   A aClass = new A();
   B bClass = new B();
   aClass.a = () => { bClass.str = "Hello"; }
 }
}

在我的 Main 方法中,我有这样的东西:

public void Main(...)
{
  C cClass = new C();
  cClass.DoSomething();

  Console.WriteLine("At this point I dont need object A or B anymore so I would like the GC to collect them automatically.");
  Console.WriteLine("Therefore I am giving GC time by letting my app sleep");
  Thread.Sleep(3000000);
  Console.WriteLine("The app was propably sleeping long enough for GC to have tried collecting objects at least once but I am not sure if A and B objects have really been collected");
 }
}

请阅读 Console.WriteLine 文本,它将帮助您理解我在这里的要求。

如果我将我对 GC 的理解应用于此示例,则 GC 将永远不会收集对象,因为 A 不能被销毁,因为它拥有 B 的实例。我说的对吗?

我怎样才能正确收集这两个对象?我是否需要将 Actions 的实例设置为 null 只是为了让 GC 在应用程序结束之前收集对象,还是 GC 已经有某种非常智能的机制知道如何销毁具有 A 和 B 等 Action 的对象?

编辑:问题是关于 GC 和正确收集对象。它与调用方法 collect() 无关。

4

2 回答 2

19

这个问题有很多问题。我不会直接回答你的问题,而是回答你应该问的问题。

让我们首先消除您对 GC 的看法。

长时间休眠会激活垃圾收集器吗?

不。

是什么激活了垃圾收集器?

出于测试目的,您可以使用GC.Collect()GC.WaitForPendingFinalizers()。这些仅用于测试目的;除非在极少数情况下,否则在生产代码中使用它们是一种不好的做法。

正常情况下触发 GC 的事情是复杂的;GC 是一台经过高度调整的机器。

就封闭的外部变量而言,垃圾收集的语义是什么?

转换为委托的 lambda 封闭外部变量的生命周期延长不短于委托的生命周期

假设我有一个类型的变量,Action它用一个封闭在引用类型的外部局部变量上的 lambda 进行初始化。为了使该变量引用的对象符合收集条件,我是否必须将类型变量设置Actionnull

在绝大多数情况下,不会。垃圾收集器非常聪明;让它做它的工作,不要担心它。最终,运行时将确定Action任何活动根都无法访问该变量,并将使其符合收集条件;然后封闭的外部变量将成为合格的。

在极少数情况下,您想丢弃对Action较早的引用,但这种情况很少见;绝大多数时候,让 GC 在不受干扰的情况下完成其工作。

是否存在外部变量的生命周期延长过长的情况?

是的。考虑:

void M()
{
    Expensive e = new Expensive();
    Cheap c = new Cheap();
    Q.longLived = ()=>c; // static field
    Q.shortLived = ()=>e; // static field
}

执行时M()会为两个代表创建一个闭包。假设这shortLived将被设置为null很快,并且longLived被设置为null在很远的将来。不幸的是,这两个局部变量的生命周期都延长到了 引用的对象的生命周期longLived,即使 onlyc仍然可以访问。直到 in中的引用失效,昂贵的资源e才会被释放。longLived

许多编程语言都有这个问题;JavaScript、Visual Basic 和 C# 的一些实现都有这个问题。在 C#/VB 的 Roslyn 版本中有一些关于修复它的讨论,但我不知道这是否会实现。

在这种情况下,解决方案是首先避免这种情况;如果其中一个代表的寿命比另一个长得多,则不要制作两个共享闭包的 lambda。

在什么情况下,不是封闭外部变量的局部变量有资格被收集?

运行时可以证明无法再次读取本地的那一刻,它引用的东西就可以收集(当然假设本地是唯一的根。)在您的示例程序中,不需要引用并保留活到方法结束。事实上,在少数情况下,GC 可以在一个线程上释放一个对象,而该对象仍在另一个线程的构造函数中!GC 可以非常积极地确定什么是死的,所以要小心。aClassbClass

面对侵略性的 GC,我如何让某些东西保持活力?

GC.KeepAlive()当然。

于 2013-05-14T19:45:42.773 回答
6

我在 C# 中使用 Actions,我想知道一旦我希望 GC 正确收集对象,是否需要将 Action 的实例设置为 null?

不必要。只要没有可以访问引用委托的对象,委托就有资格进行 GC。

话虽这么说,在您的示例中,aClass并且bClass仍然是有效变量,并且是指可访问的对象。这意味着它aClass.a仍然可以访问,并且不符合 GC 条件,因此不会被收集。

如果您希望这些被垃圾回收,您需要显式地将对象引用 ( aClass) 设置为 null,以便A实例及其包含的委托不再是可访问的对象,然后您必须显式调用GC.Collect以触发GC,因为没有什么会导致 GC 在您的代码中触发。

于 2013-05-14T19:19:06.930 回答