Object Disposed异常和多线程应用程序

ale*_*oot 5 c# multithreading timer

我有一个启动System.Threading.Timer的应用程序,然后这个计时器每5秒从链接数据库读取一些信息并在主要应用程序形式上更新GUI;

由于System.Threading.Timer为Tick事件创建另一个线程,我需要使用Object.Invoke在主应用程序表单上更新用户界面,代码如下:

this.Invoke((MethodInvoker)delegate()
  {
       label1.Text = "Example";
  });
Run Code Online (Sandbox Code Playgroud)

该应用程序工作得很好,但有时当用户关闭主窗体然后关闭应用程序时,如果timer_tick事件上的第二个线程正在更新主线程上的用户界面,则用户将获得ObjectDisposedException.

如何在关闭主窗体之前停止并关闭线程计时器,然后避免对象处置异常?

Jar*_*Par 7

这是一个棘手的主张,因为您必须确保给定的Close事件具有以下内容

  1. 计时器停止.这是相当直接的
  2. 当代理运行时,不会处理正在更新的控件.再次直截了当.
  3. 当前正在运行计时器滴答的代码已完成.这更难但可行
  4. 没有挂起的Invoke方法.这要完成起来要困难得多

我以前遇到过这个问题而且我发现防止这个问题是非常有问题的,并且涉及很多混乱,难以维护的代码.相反,更容易捕获这种情况可能产生的异常.通常我通过如下包装Invoke方法来实现

static void Invoke(ISynchronizedInvoke invoke, MethodInvoker del) {
  try {
    invoke.Invoke(del,null);
  } catch ( ObjectDisposedException ) {
    // Ignore.  Control is disposed cannot update the UI.
  }
}
Run Code Online (Sandbox Code Playgroud)

如果你对后果感到满意,忽略这个例外就没有任何内在错误.也就是说,如果您已经处理好UI,那么它就不会更新了.我当然是:)

上面没有解决问题#2,但仍需要在您的代理中手动完成.使用WinForms时,我经常使用以下重载来删除该手动检查.

static void InvokeControlUpdate(Control control, MethodInvoker del) {
  MethodInvoker wrapper = () => {
    if ( !control.IsDisposed ) {
      del();
    }
  };
  try {
    control.Invoke(wrapper,null);
  } catch ( ObjectDisposedException ) {
    // Ignore.  Control is disposed cannot update the UI.
  }
}
Run Code Online (Sandbox Code Playgroud)

注意

汉斯指出,ObjectDisposedException这不是可以从Invoke方法中提出的唯一例外.还有其他几个,至少包括InvalidOperationException你需要考虑处理.


Han*_*ant 3

System.Timers.Timer 是一个可怕的类。没有什么好的方法可以可靠地阻止它,总是有一场竞赛,你无法避免它。问题在于它的 Elapsed 事件是从线程池线程引发的。您无法预测该线程何时实际开始运行。当您调用 Stop() 方法时,该线程很可能已经添加到线程池中,但尚未开始运行。它受 Windows 线程调度程序和线程池调度程序的约束。

你甚至不能通过任意延迟关闭窗口来可靠地解决它。在最极端的情况下,线程池调度程序可以将线程的运行延迟最多 125 秒。您可以通过延迟关闭几秒钟来减少异常的可能性,它不会为零。延迟 2 分钟关闭是不现实的。

只是不要使用它。要么使用 System.Threading.Timer 并将其设为一次性计时器,然后在事件处理程序中重新启动。或者使用System.Windows.Forms.Timer,它是同步的。

此处应选择 WF 计时器,因为您使用 Control.Invoke()。在您的 UI 线程空闲之前,委托目标不会开始运行。与 WF 计时器的行为完全相同。