C#动作和GC

sno*_*hog 2 c# delegates garbage-collection

我在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"; }
 }
}
Run Code Online (Sandbox Code Playgroud)

在我的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");
 }
}
Run Code Online (Sandbox Code Playgroud)

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

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

我该如何正确收集这两个对象?我是否需要将Actions的实例设置为null以便让GC在应用程序结束之前收集对象,或者GC是否已经知道如何销毁具有诸如A和B之类的操作的对象的某种非常智能的机制?

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

Eri*_*ert 19

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

让我们先解读你对GC的看法.

长时间睡觉会激活垃圾收集器吗?

没有.

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

出于测试目的,您可以使用GC.Collect()GC.WaitForPendingFinalizers().仅将它们用于测试目的; 除了在一些非常罕见的情况下,在生产代码中使用它们是一种不好的做法.

在正常情况下,触发GC的事情很复杂; GC是一种高度调整的机器.

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

寿命,能转化为一个委托的λ的封闭在外层变量的被扩展不大于委托的寿命更短.

假设我有一个类型的变量,Action它使用lambda初始化,该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
}
Run Code Online (Sandbox Code Playgroud)

M()执行,是您在委托创建一个封闭.假设shortLivednull很快设置,并将在未来longLived设置为null远.不幸的是,两个局部变量的生命周期都延长到所引用的对象的生命周期longLived,即使只能c仍然可以访问它们.e在引用longLived已经死亡之前,不会释放昂贵的资源.

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

在这种情况下,解决方案是首先避免这种情况; 如果其中一个代表的生活时间比另一个代表长得多,那么就不要让两个lambdas共享一个闭包.

在什么情况下,不是封闭的外部变量的本地变得可以收集?

运行时可以证明本地无法再次读取的那一刻,它引用的东西变得可以收集(假设本地是唯一的根当然.)在你的示例程序中,没有要求引用和保留活到这个方法结束.实际上,有一些罕见但可能的情况,GC可以在一个线程上解除分配对象,而它仍然在另一个线程的构造函数中!GC可以非常积极地确定什么是死的,所以要小心.aClassbClass

面对激进的GC,我如何保持活力?

GC.KeepAlive() 当然.


Ree*_*sey 6

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

不必要.只要没有引用该委托的对象,该委托就有资格获得GC.

话虽这么说,在你的榜样,aClassbClass仍然有效的变量,并指可及的对象.这意味着aClass.a仍然可以访问,并且不符合GC的条件,因此不会收集它.

如果您希望将这些文件进行垃圾回收,则需要将对象reference(aClass)显式设置为null,以便A实例及其包含的委托不再是可访问的对象,然后您必须显式调用GC.Collect以触​​发GC,因为没有任何东西会导致GC在您的代码中触发.

  • @snowyhedgehog:在30秒内,GC的工作方式如下:想象一下,你的白板上装满了带箭头的红色方框.选择一些盒子作为"生活"并将它们涂成绿色.现在,绿色方框指向的每个红色方框也将其绿色着色.继续这样做,直到绿色框不再指向红色框.现在*擦除白板上的所有红色框*,并将所有绿色框重新着色为红色.这就是垃圾收集器的工作原理.当抖动知道"aClass"永远不再读取时,它会停止将其着色为绿色; 它保持红色,然后消失. (4认同)
  • 这些评论中的错误信息水平非常高.@snowyhedgehog:你应该只相信你在这些评论中读到的一半,因为这是关于多少是正确的. (3认同)
  • 因此,关于*必须*在程序中以什么顺序发生的所有这些假设都是错误的假设.我们可以在特定的实现*中讨论**发生*,但这是一个实现细节,而不是一个要求. (3认同)