关闭窗体时计时器未被释放

9
我将尝试理解为什么一个Windows.Forms.Timer不会在创建它的form被销毁时被释放。我有这个简单的表格:
public partial class Form1 : Form {

    private System.Windows.Forms.Timer timer;

    public Form1() {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e) {
        timer = new Timer();
        timer.Interval = 1000;
        timer.Tick += new EventHandler(OnTimer);
        timer.Enabled = true;
    }

    private void OnTimer(Object source, EventArgs e) {
        Debug.WriteLine("OnTimer entered");
    }

    private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
        this.Dispose();
    }
}

当我关闭它时,会调用this.Dispose,但计时器触发事件仍将继续被调用。我认为Dispose会释放所有由已处理对象拥有的对象。这是不真实的吗?Timer有特定的行为吗?
目前,我发现处理计时器的方法是执行timer.Tick -= OnTimer;,然后在Form1_FormClosed事件中调用它。这是个好解决方案还是应该采用其他方法?
编辑
或者简单地做如下更改是否更好:
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
    timer.Dispose();
    this.Dispose();
}

?


3
еә”иҜҘеңЁ IDisposable ж–№жі• Dispose дёӯеӨ„зҗҶиө„жәҗйҮҠж”ҫпјҢиҖҢдёҚжҳҜеңЁ FormClosing дёӯгҖӮ - Oskar Kjellin
1
FormClosing 事件中,您应该停止计时器 timer.Enabled = false; 然后 timer.Dispose(); timer = null; - Marco
1
@Marco:当你使用 timer.Dispose(); 时,timer.Enabled = false;timer = null; 的作用是什么?难道 Dispose() 不会处理所有的事情吗? - Otiel
@Otiel:注意,释放一个对象不等于将其赋值为 null。如果你使用 timer.Dispose(),稍后尝试访问 timer,则会出现异常;因此,如果你知道某个对象可能已被删除,可以检查 if (timer != null) ... 但是如果你刚刚释放了它,这个条件会是假的,那么在执行代码时就会出现异常。因此,最好同时进行释放和赋值为 null,以确保在代码中在对象被释放之后就不再使用它。 - Marco
2
从工具箱中拖动一个计时器到设计师所在的表单上。现在一切都是自动的。 - Hans Passant
显示剩余4条评论
5个回答

14

正如我在之前的评论中告诉你的那样,你应该尝试:

private Form1_FormClosing(...)
{
    timer.Stop();
    timer.Tick -= new EventHandler(OnTimer);
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) 
{
    timer.Dispose();
    timer = null;   
} 

这很好,因为它防止计时器再次循环(在FormClosing中),并且您可以在其他部分检查(本示例中不是因为您正在关闭窗体,但这是一个例子)是否在使用它之前已经删除了该对象(计时器)。
因此,在其他部分,您可以执行以下操作:

if (timer != null) // Note: this is false if you just use timer.Dispose()
{
    ....
}

5
唯一正确的处理可释放成员的 IDisposable 类的方法是在其 Dispose(bool disposing) 方法内进行(请参阅MSDN 文章)。换句话说,您可以打开自动生成的 Form.Designer.cs 文件,并将其放置在适当的方法中。
另一方面,如果通过 VS 设计器添加 Timer(而不是自己实例化),它将被添加到 components 容器中:
// autogenerated inside Form.Designer.cs, InitializeComponent() method
this.timer = new System.Windows.Forms.Timer(this.components);

components成员被处理时,应该适当地处置它:

// autogenerated inside Form.Designer.cs, Dispose(bool disposing) method
if (disposing && (components != null))
{
    components.Dispose();
}

如果你想自己完成这个功能,请记住,设计师只有在认为有必要时才实例化组件。因此,在你的情况下,组件可能会是null

解决这个问题最简单的方法是:从工具箱中拖动计时器,并在Form_Load处理程序中启动它。


1
 private void Form1_FormClosed(object sender, FormClosedEventArgs e) 
 {        timer.Stop();    } 

我不应该在窗体关闭事件中放置 this.Dispose();,只需停止计时器即可。

1

EventHandler是持久化引用,移除引用并在Closing事件上停止计时器或者在不需要的时候立即停止。如果你想检查计时器是否被释放,请在Closed事件中检查。


1

简单的Timer.Dispose()会删除计时器资源,包括停止计时器在未来触发。

然而,在Dispose()返回后,仍可能存在正在执行或等待执行的回调函数在线程池的工作队列中。

第二个重载Timer.Dispose(WaitHandle)将在计时器的所有回调函数完成后向传入的对象发送信号。这可以是任何WaitHandle,例如ManualResetEvent

为了简化操作,您可以传递WaitHandle.InvalidHandleTimer.Dispose()仅在所有回调函数完成后返回。这避免了分配真正的事件对象,通常是您想要做的。

由于WaitHandle是抽象的,您需要使用一些技巧,即创建自己的子类。

class InvalidWaitHandle : WaitHandle {}
Timer tmr = new Timer(...);
tmr.Dispose(new InvalidWaitHandle);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接