当您使用Timer
或Thread
只会在程序的整个生命周期内运行时,您是否需要保留对它们的引用以防止它们被垃圾回收?
请撇开以下程序可以将timer
作为类中的静态变量这一事实,这只是一个展示问题的玩具示例。
public class Program
{
static void Main(string[] args)
{
CreateTimer();
Console.ReadLine();
}
private static void CreateTimer()
{
var program = new Program();
var timer = new Timer();
timer.Elapsed += program.TimerElapsed;
timer.Interval = 30000;
timer.AutoReset = false;
timer.Enabled = true;
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
var timerCast = (Timer)sender;
Console.WriteLine("Timer fired at in thread {0}", GetCurrentThreadId());
timerCast.Enabled = true;
}
~Program()
{
Console.WriteLine("Program Finalized");
}
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
}
上述示例中是否可以收集计时器?我跑了一段时间,我从来没有得到异常,也没有一条消息说~Program()
被调用。
更新:我从this question(thanks sethcran)发现CLR会跟踪线程,但我仍然希望得到有关计时器的答案。
答案 0 :(得分:43)
如果您没有在某处存储对它的引用,那么这只是System.Threading.Timer类的一个问题。它有几个构造函数重载,采用 state 对象的重载很重要。 CLR关注该状态对象。只要在某处引用它,CLR就会将计时器保留在其计时器队列中,并且计时器对象不会被垃圾收集。大多数程序员都不会使用那个状态对象,MSDN文章肯定不会解释它的作用。
System.Timers.Timer是System.Threading.Timer类的包装器,使其更易于使用。特别是,只要启用了计时器,它就会使用该状态对象并保持对它的引用。
请注意,在您的情况下,计时器的Enabled属性在进入Elapsed事件处理程序时为false,因为您有AutoReset = false。因此,一旦计时器进入您的事件处理程序,它就有资格收集。但是,您可以通过引用 sender 参数来避免麻烦,需要将Enabled设置为true。这使得抖动报告成为参考,因此您没有问题。
小心Elapsed事件处理程序。在没有诊断的情况下,将吞下该方法内引发的任何异常。这也意味着您不会将Enabled设置为true。您必须使用try / catch来做一些合理的事情。如果你不打算故意结束你的程序,至少你需要让你的主程序知道某些东西不再起作用了。在finally子句中放置Enabled = true可以避免收集计时器垃圾,但是可能会让程序一次又一次地抛出异常。
答案 1 :(得分:2)
将此代码添加到程序并运行它。你会看到没有收集计时器。
private void DoStuff()
{
CreateTimer();
Console.WriteLine("Timer started");
int count = 0;
for (int x = 0; x < 1000000; ++x)
{
string s = new string("just trying to exercise the garbage collector".Reverse().ToArray());
count += s.Length;
}
Console.WriteLine(count);
Console.Write("Press Enter when done:");
Console.ReadLine();
}
private void Ticktock(object s, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine("Ticktock");
}
private void CreateTimer()
{
System.Timers.Timer t = new System.Timers.Timer(); // Timer(Ticktock, null, 1000, 1000);
t.Elapsed += Ticktock;
t.Interval = 1000;
t.AutoReset = true;
t.Enabled = true;
}
因此,您的问题的答案似乎是计时器不符合收集条件,如果您没有保留对它的引用,则不会收集计时器。
有趣的是,如果您使用System.Threading.Timer
运行相同的测试,您会发现 收集了
答案 2 :(得分:2)
让我们进行一个实验:
private static void UnderTest() {
// Timer is a local varibale; its callback is local as well
System.Threading.Timer timer = new System.Threading.Timer(
(s) => { MessageBox.Show("Timer!"); },
null,
1000,
1000);
}
...
// Let's perform Garbage Colelction manually:
// we don't want any surprises
// (e.g. system starting collection in the middle of UnderTest() execution)
GC.Collect(2, GCCollectionMode.Forced);
UnderTest();
// To delay garbage collection
// Thread.Sleep(1500);
// To perform Garbage Collection
// GC.Collect(2, GCCollectionMode.Forced);
到目前为止
Thread.Sleep(1500);
和GC.Collect(2, GCCollectionMode.Forced);
,我们会看到消息框出现:计时器正在工作GC.Collect(2, GCCollectionMode.Forced);
,将不会显示 :计时器开始计时,而不是被收集Thread.Sleep(1500);
和GC.Collect(2, GCCollectionMode.Forced);
的注释,则将显示一个单个消息框:计时器启动,关闭一条消息框,然后收集计时器所以Timer
和其他对象实例一样被收集。