如果表单应用程序关闭 C#,如何防止发生 Timer Elapsed 事件

How to prevent Timer Elapsed event from occurring if forms application closes C#

我在 windows 表单应用程序 (Visual C#) 中有一个计时器,当我想退出该应用程序时出现问题。

定时器被定义为表单的成员 class:

 partial class Form1 
     {
        //These are the members in question:
        internal ComACRServerLib.Channel channel;
        private System.Timers.Timer updateStuff;
     }

计时器是 declared/constructed 在表单应用程序的构造函数中:

public Form1()
        {
            InitializeComponent();
            updateStuff = new System.Timers.Timer();
            updateStuff.Elapsed += new System.Timers.ElapsedEventHandler(updateStuff_Elapsed);
        }

按下按钮即可启动和配置计时器:

 private void btnAcquire_Click(object sender, EventArgs e)
    {
        updateStuff.Interval = 100;
        updateStuff.Enabled = true;
        updateStuff.AutoReset = true;
        updateStuff.Start();
    }

当计时器结束时,它调用 updateStuff_Elapsed,它获取要用 setText 显示的信息(有一些代码可以确保对 setText 的调用是线程安全的) .

 private void updateStuff_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
       {
              if (!channel.isOffline)
              {
                  object[] status = channel.GetACRCustom("P6144");
                  setText(System.Convert.ToString(status[0]));
              }
       }

 public delegate void setTextDelegate(string text);

 public void setText(string text)
       {
             if (this.lblTest.InvokeRequired == true)
             {
                  setTextDelegate d = new setTextDelegate(setText);
                  this.Invoke(d, new object[] { text });
             }
             else
             {
                  lblTest.Text = text;
             }
       }

在应用程序退出时,我尝试通过以下方式摆脱计时器并防止它再次触发:

protected override void Dispose(bool disposing)
        {

            if (disposing && (components != null))
            {
                updateStuff.AutoReset = false;
                updateStuff.Stop();
                updateStuff.Close();
                updateStuff.Dispose();

                components.Dispose();
            }
            base.Dispose(disposing);
        }

但是如果计时器自动运行并且我退出程序,我总是会收到错误消息,即 Timer Elapsed 事件 updateStuff_elapsed 调用的例程正在尝试使用已经释放的资源!尽管我已尽力在处理发生之前停止并销毁计时器。

如何在应用程序关闭时阻止计时器触发?

编辑

我尝试移动 Dispose 代码以尝试强制关闭计时器,但没有成功。我还尝试使用 updateStuff.Elapsed -= updateStuff_Elapsed 在停止和处理之前删除事件调用;

protected override void Dispose(bool disposing)
        {
            //now this code HAS to run always.
            updateStuff.Elapsed -= updateStuff_Elapsed;
            updateStuff.AutoReset = false;
            updateStuff.Stop();
            updateStuff.Close();
            updateStuff.Dispose();

            if (disposing && (components != null))
            {           
                components.Dispose();
            }
            base.Dispose(disposing);
        }

处理处置事件为时已晚(在垃圾收集期间)。您需要对 FormClosing 事件执行清理过程。

System.Timers.Timer 的文档中所述,Timer 的事件处理程序调用在 ThreadPool 线程上排队。因此,您必须假设事件处理程序可以一次调用多次,或者可以在禁用 Timer 后调用。因此,事件处理程序的设计必须能够正确处理这些情况。

首先,将定时器的SynchronizingObject属性设置为Form的实例。这会将事件处理程序的所有调用编组到 UI 线程,因此我们不需要为锁定表单字段而烦恼(我们将始终从同一个 UI 线程访问所有内容)。使用此 属性 设置,您也不需要在 setText 方法中调用 this.Invoke(...)。

public Form1()
{
    updateStuff = new System.Timers.Timer();
    updateStuff.SynchronizingObject = this;
    ...
}

public void setText(string text)
{
    lblTest.Text = text;
}

然后创建标志,让您知道定时器是否已被处理。然后只需在事件处理程序中检查此标志:

partial class Form1 
{
    private bool Disposed;
    ....
}

protected override void Dispose(bool disposing)
{
    if (disposing && (components != null))
    {
            updateStuff.Dispose();
            Disposed = true;
    }

    base.Dispose(disposing);
}

private void updateStuff_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    if(!Disposed)
    {
          if (!channel.isOffline)
          {
              object[] status = channel.GetACRCustom("P6144");
              setText(System.Convert.ToString(status[0]));
          }
    }
}

进一步阐述 Ron 和 Marc 所说的话, 解决方案是覆盖 class FormOnFormClosing 方法,其中 Form1 是 subclassed:

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        //It appears it needs everything here to be turned off...amazing.
        updateStuff.Elapsed -= updateStuff_Elapsed;
        updateStuff.AutoReset = false;
        updateStuff.Stop();
        updateStuff.Dispose();
        base.OnFormClosing(e);
    }

另一种解决方案是将计时器放在与用户界面相同的线程上,如 Nuf 所述。