【问题标题】:does nulling a System.Threading.Timer stop it?清空 System.Threading.Timer 会阻止它吗?
【发布时间】:2010-08-30 01:12:16
【问题描述】:

如果我有一个活动的System.Threading.Timer 并将其设置为 null,它会停止吗?

我知道打电话给.Dispose() 更合适,但我希望得到书面问题的答案。

public class Foo
{
   private System.Threading.Timer _timer;
   public Foo()
   {
      // initialize timer
   }

   public void KillTimer()
   {
      _timer=null;
   }
}

更新:

经过反复讨论是否将对 System.Threading.Timer 的单个引用设置为 null 确实会导致停止它,这表明

  1. 没有延迟引用,例如事件列表,因为线程计时器采用单一回调并且不公开事件。
  2. 如果 GC 收集,finalizer 确实会释放 TimerBase 并停止计时器。

尖峰

using System;
using System.Threading;

namespace SO_3597276
{
    class Program
    {
        private static System.Threading.Timer _timer;

        static void Main(string[] args)
        {
            _timer = new Timer((s) => Console.WriteLine("fired"), null, 1000, Timeout.Infinite);
            _timer = null;
            GC.Collect();
            Console.ReadKey();
        }
    }
}

不调用定时器回调。删除GC.Collect() 并调用回调。

谢谢大家。

【问题讨论】:

  • 为什么您认为仅仅因为您将变量设置为 null 就不再有对 Timer 的引用?大概 Timer 已被添加到某些系统事件队列中,以便注意到 Timer 何时触发。你知道那个事件队列是否使用 Wea​​kReference 来引用 Timer 吗? (WeakReference是Java的概念:不知道.NET有没有类似的概念)
  • @adrian - 是的,.net 有 System.WeakReference。关于您的问题:上面的代码证明没有更多的引用,否则计时器将在 readkey 块中触发 1 秒。在更技术性的说明中,当创建计时器时,回调最终会被提供给private extern void AddTimerNative。显然,通常是 .net 委托的内容是在非托管代码中处理的,而不是被视为引用。我不会假装完全理解那里发生的事情,但与上面的代码相关是有意义的。我希望能对此进行更多讨论。
  • @adrian - 并且盲目地向问题投掷飞镖,在我看来,突出的问题是它是一个函数指针,它是一个被传递的函数指针,而不是一个委托,因此没有引用计时器。

标签: c#


【解决方案1】:

为什么会这样?

考虑:

  System.Threading.Timer t = ...;
  System.Threading.Timer q = t;
  q = null; // Should this stop it as well?

设置为 null 是您对变量执行的操作,而不是您对对象执行的操作。 Timer 无法知道您将特定变量设置为 null,也无法据此采取行动。

编辑:

为了解决编辑问题,即使在有唯一引用的情况下,也不能保证计时器会停止,因为在引用设置为 null 后,GC 可能不会运行。这也不是完全不可能的,Microsoft .NET 实现使用分代收集器,并且静态字段可能会在托儿所收集中幸存下来并被提升到老一代。如果您的程序具有相对稳定的内存配置文件,则可能永远不会收集旧代(并且通过扩展,终结器将在程序结束之前运行)。

【讨论】:

  • GC 会自动清理未引用的对象,因此将其设置为 null“可能”会产生效果。
  • @Russell 如果收集器运行,并且这是对计时器的唯一引用。将变量设置为 null 和停止计时器之间没有直接的因果关系。
  • 原因/结果是“如果计时器没有更多的引用”,那么 GC 将处理它。
  • “正确编写的程序不能依赖终结器的副作用。” blogs.msdn.com/b/oldnewthing/archive/2010/08/09/10047586.aspx
  • @code:你说有事件,对吧?使该字段为空不会对事件列表中的委托实例做任何事情,它们都包含对对象的引用。真的,答案是:不要那样做,它不会像你认为的那样做。
【解决方案2】:

我知道你在问System.Threading.Timer 课程,但我想指出一些相当重要的事情。

到目前为止提供的答案都很好。 LoganSLaks 是正确的,将任何变量设置为 null 对先前分配变量的对象没有直接影响。 Russell is right 说当垃圾收集器确实 最终处置计时器时,它 停止。

SLaks 指出,在将计时器引用设置为null 后,可能会有延迟引用。在一个System.Threading.Timer 引用的简单示例中,情况并非如此。

但是,例如,如果您有一个System.Timers.Timer,并且您处理它的Elapsed 事件,那么将其设置为null 留下一个参考和计时器永远继续运行。

因此考虑以下代码示例:

var t = new System.Timers.Timer(1000.0);
t.AutoReset = true;
t.Elapsed += (sender, e) => Console.WriteLine(DateTime.Now);

Console.Write("Press Enter to start the timer.");
Console.ReadLine();
t.Start();

Console.Write("Press Enter to set t to null.");
Console.ReadLine();

// This will not stop the timer. It actually does nothing at all to the timer
// to which t has been assigned.
t = null;

Console.Write("Press Enter again to perform a garbage collection.");
Console.ReadLine();

// This STILL will not stop the timer, as t was not the only reference to it
// (we created a new one when we added a handler to the Elapsed event).
GC.Collect();

Console.Write("t is null and garbage has been collected. Press Enter to quit.");
Console.ReadLine();

在上面的示例中,由于代码保留了对t 的引用以处理其Elapsed 事件,因此计时器将永远不会停止。

再一次,我意识到这不是你问的课程;我只是提出这一点,指出事实上你是否有对给定对象的任何引用并不总是很明显。


更新:关于我上面的陈述是否同样适用于System.Threading.Timer 对象的话题似乎出现了一些混乱; 不是。为了验证这一点,请考虑对上述代码进行以下修改:

Console.Write("Press Enter to start the timer.");
Console.ReadLine();

var t = new System.Threading.Timer(
    state => { Console.WriteLine(DateTime.Now); },
    null,
    0,
    1000
);

Console.Write("Press Enter to set t to null.");
Console.ReadLine();

// This will not stop the timer. It actually does nothing at all to the timer
// to which t has been assigned. HOWEVER, if/when the GC comes around to collect
// garbage, it will see that said timer has no active references; and so it will
// collect (and therefore finalize) it.
t = null;

Console.Write("Press Enter again to perform a garbage collection.");
Console.ReadLine();

// This WILL cause the timer to stop, as there is code in the type's
// finalizer to stop it.
GC.Collect();

Console.Write("t is null and garbage has been collected. Press Enter to quit.");
Console.ReadLine();

这就是为什么它不适用于 System.Timers.Timer(或任何有事件的类型,实际上)

当我们像这样定义事件处理程序时,很容易错过答案:

t.Elapsed += (sender, e) => Console.WriteLine(DateTime.Now);

如果我改为这样定义我的处理程序会怎样?

t.Elapsed += (sender, e) => Console.WriteLine(sender.GetType());

哦,对了!那个sender 没有人注意的论点!

.NET 提供的事件处理基础结构要求对象处理 事件维护对对象引发 事件的引用。否则,EventHandler 代表及其所有表亲签名所提供的合同将被违反。

这个故事的寓意是:一旦您将处理程序添加到事件中,您就创建了对对象的新引用。在此之后允许该对象被垃圾收集的唯一方法是删除处理程序 - 但如果您将对该对象的唯一其他引用设置为 null (这是非常.NET 程序可能存在“内存泄漏”的几个示例)。

【讨论】:

  • 非常好。还有 Windows Forms Timer 和 WPF DispatcherTimer,如果它们也被内部引用跟踪,我不会感到惊讶。
  • dan,john 提出了一个很好的观点,似乎与 slaks 的答案和这个..事件列表中的处理程序委托重叠。不完全清楚该列表不是计时器的成员,因此与它一起为空。
  • 没关系。出于某种原因,我一直不理解将引用归零并不会使对象归零。所以即使使用 System.Threading.Timer 也有挥之不去的引用
  • @code 诗人:如果你只有一个引用,System.Threading.Timer 就没有挥之不去的引用。如果您将该引用设置为null,则不再有活动引用,并且当计时器被垃圾收集(并因此最终确定)时,它确实会停止。 System.Timers.Timer 不同的原因是,一旦你将一个处理程序附加到它的Elapsed 事件,马上就会有一个对计时器的新引用,它永远不会被垃圾回收。我会更新我的答案,希望能更清楚地解释这一点。
  • @dan - 你是对的。这种来回让我头晕目眩。我继续前进并增加了证据。谢谢你提示我。
【解决方案3】:

不一定。将其设置为 null,删除对它的所有引用,并依赖垃圾收集器来处理它。

如果计时器在 GC 到达它之前就关闭了,它将触发事件。

【讨论】:

  • 我喜欢它,因为它 1) 同意我的假设,2) 因为它直接解决了这个问题,这意味着有一个单一的参考,实际上,将其设置为null 将删除对它的任何引用。我希望看到“删除唯一的参考”或类似的东西,但足够接近 gubment 工作。其他答案虽然正确且内容丰富,但在仍然支持您的答案的同时略微错过了重点。我倾向于接受这个答案。
【解决方案4】:

不,它不会停止。

将变量设置为null 不会直接产生任何副作用,例如停止计时器。 (除非是财产)

由于定时器有其他引用,GC不会收集,永远不会停止。

【讨论】:

  • 嘿 slaks。我们还在谈论哪些其他参考资料?
  • @kbrimington:工作示例是不现实的,因为在这个应用程序中,垃圾收集器可能永远不会运行。添加GC.Collect 行,您将看到 then 计时器停止。所以说“它永远不会停止”并不准确。在设置为null 之后,它可能不会(事实上,几乎肯定不会不会)以任何确定性方式停止。
  • @kbrimington - 您可能应该添加代码示例作为您自己的答案。我不知道编辑一个 75k+'ers 的答案来添加你自己的代码真的很尊重。例如,如果它有错误或没有说明 SLaks 的观点,它会反映在他身上,因为大多数人不会回顾历史。
  • @Josh - 很公平。我只希望能提供帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-08
相关资源
最近更新 更多