【问题标题】:System.Windows.Forms.Timer and Garbage Collection with C#System.Windows.Forms.Timer 和 C# 的垃圾收集
【发布时间】:2020-05-28 03:30:20
【问题描述】:

如果我创建一个不与 Form.Designer.cs 文件关联的 System.Windows.Forms.Timer(例如,我只是在我的代码中的任何位置实例化计时器),然后我创建一个 System.ComponentModel.Container 组件并创建一个要添加到它的控件,例如 NotifyIcon(此 notifyIcon 不会有与之关联的图标)并将该 notifyIcon 添加到我的组件对象然后实例化 forms.timer 与组件作为计时器构造函数的参数,将如果计时器的启用属性在程序的生命周期内设置为 false 并且计时器在程序的生命周期内从未被丢弃,那么计时器是否会被 GC-ed?或者只要不直接处理该组件,计时器就不会被垃圾回收吗?

这是一个代码示例:

NotifyIcon notifyIcon = new NotifyIcon();
notifyIcon.Visible = false;
System.ComponentModel.IContainer components = new System.ComponentModel.Container();
components.add(notifyIcon);
System.Windows.Forms.Timer formTimer = new System.Windows.Forms.Timer(components);
formTimer.Enabled = true;
formTimer.Enabled = false;

感谢阅读

【问题讨论】:

  • 为什么不保留对计时器的引用?
  • 把它存储在一些私有字段中——GC收集没有引用的对象,它不关心这个对象是启用还是禁用
  • 启用与什么有什么关系?是否有参考或目前是否在范围内才是最重要的。
  • Or as long as that component is not disposed of directly, would the timer be safe from garbage collection? 否。定时器在无法访问时将有资格被 GC。
  • whether it is currently in scope @shox 并不重要(它可以看起来在调试时很重要 - 但实际上并不重要)。重要的是是否有对它的实时引用(即可达)。 docs.microsoft.com/en-us/archive/blogs/cbrumme/…

标签: c# winforms timer garbage-collection


【解决方案1】:

理论

如果对象 A 持有对对象 B 的引用,并且您将 null 分配给 A,那么一段时间后 A 会被垃圾回收。如果还有其他人引用了 B,则 B 不是垃圾收集。但是,如果 A 是最后一个对 B 的引用,那么 B 也将被收集:

Person Joseph = new Person
{
    Id = 1, 
    Address = new Address {Id = 10, ...},
    ...
};

Joseph.Address = new Address {Id = 11, ...}

一段时间后,ID 为 10 的地址将被垃圾回收,因为没有人再持有对它的引用。

Person Mary = new Person
{
    Id = 2, 
    Address = Joseph.Address,
    ...
};
Joseph = null;

一段时间后,ID 为 1 的 Person 将被垃圾回收,因为没有人再持有对它的引用。但是,ID 为 11 的地址不会被垃圾回收,因为 ID 为 2 的人持有对它的引用。

mary = null;

现在没有人持有对 ID 为 11 的地址的引用,因此它将被垃圾回收。

回到你的问题

  • 您创建一个新的 Container 对象
  • 您创建一个新的 NotifyIcon 对象。
  • 您将此 NotifyIcon 对象添加到组件对象中
  • 您创建一个新的 Timer 对象,使用命令计时器将自身添加到 Container 的构造函数。

因此,Container 对象同时引用了已创建的 NotifyIcon 和 Timer 对象。只要这些对象没有从 Container 中移除,并且容器没有 Disposed 或垃圾回收,这个 Container 就会持有这些引用,因此 notifyIcon 和 Timer 都不会被垃圾回收。

components.Dispose();

根据reference source of System.ComponentModels.Container,这将执行以下操作:

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        lock (syncObj) {
        while (siteCount > 0)
        {
            ISite site = sites[--siteCount];
            site.Component.Site = null;
            site.Component.Dispose();
        }
        sites = null;
        components = null;
    }
}

所以Container.Dispose()会调用所有添加组件的Dispose(),然后释放对添加组件的引用。

  • 如果您在其他地方引用了这些组件,那么这些组件将不会被垃圾回收。但是,由于它们是 Disposed,因此通常不可用。
  • 如果 Container 是唯一一个有引用的容器,那么在 Container.Dispose() 之后,NotifyIcon 和 Timer 就有资格被收集。
  • 因为您仍然持有对 Container 的引用,所以不会收集 Container 本身。既然是 Disposed,Container 就不能再使用了。
  • 为确保容器被收集,要么让引用超出范围,要么将 null 分配给它。

在每个Form.Dispose() 中,您会发现类似于:

private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
    if (disposing && (components != null))
    {
        components.Dispose();
    }
    base.Dispose(disposing);
}

当 Form 被 Disposed 时, this.components 也被 Disposed,这意味着它持有的所有 IComponent 都被 Disposed,并且引用被删除。甚至对 Container.ComponentCollection 的引用也被删除。请注意,表单仍然包含对 this.components 的引用,因此即使 this.components 不能再使用。

后者有点奇怪:如果您不再持有对 NotifyIcon / Timer 的引用,它们就会被垃圾收集,但是只要表单存在,this.Components 就不会。如果 Form.Dispose 也发布了对 this.components 的引用会更整洁

【讨论】:

  • 感谢您抽出宝贵时间阅读这篇文章并写下深思熟虑的回复。您的解释不仅对我很有帮助,对其他人也很有帮助。我将此标记为已接受的答案。感谢您的宝贵时间!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-01
  • 1970-01-01
  • 2012-03-21
  • 2013-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多