对象处理异常和多线程应用程序

发布于 2024-09-17 07:40:41 字数 434 浏览 2 评论 0原文

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

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

this.Invoke((MethodInvoker)delegate()
  {
       label1.Text = "Example";
  });

该应用程序运行良好,但有时当用户关闭主窗体,然后关闭应用程序,如果 timer_tick 事件上的第二个线程正在更新主线程上的用户界面,则用户会收到 ObjectDisposeException。

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

I have an application that start System.Threading.Timer, then this timer every 5 seconds read some information from a linked database and update GUI on main form of application;

Since the System.Threading.Timer create another thread for the Tick event, i need to use Object.Invoke for updating User Interface on the main Form of application with code like this :

this.Invoke((MethodInvoker)delegate()
  {
       label1.Text = "Example";
  });

The app work very well, but sometimes when the user close the main form and then close the application, if the second thread on timer_tick event is updating the user interface on main thread the user get an ObjectDisposedException.

How can i do for stop and close the threading timer before closing the main form and avoiding then Object disposed exception ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

各自安好 2024-09-24 07:40:41

这是一个有点棘手的提议,因为您必须确保在给定的关闭事件上执行以下操作:

  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.
  }
}

如果您对结果感到满意,那么忽略此异常并没有本质上的错误。也就是说,如果您对用户界面在处理后不更新感到满意的话。我当然是:)

不过上面的内容并没有解决问题#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.
  }
}

注意

正如 Hans 所指出的,ObjectDisposeException 并不是 Invoke 方法引发的唯一异常。还有其他几个,至少包括您需要考虑处理的 InvalidOperationException

This is a bit of a tricky proposition as you must ensure the following on a given Close event

  1. The timer is stopped. This is fairly straight forward
  2. The control being updated isn't disposed when the delegate is run. Again straight forward.
  3. The code currently running off of a timer tick has completed. This is harder but doable
  4. There are no pending Invoke methods. This is quite a bit harder to accomplish

I've run into this problem before and I've found that preventing this problem is very problematic and involves a lot of messy, hard to maintain code. It's much easier to instead catch the exceptions that can arise from this situation. Typically I do so by wrapping the Invoke method as follows

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

There is nothing inherently wrong with ignoring this exception if you're comfortable with the consequences. That is if your comfortable with the UI not updating after it's already been disposed. I certainly am :)

The above doesn't take care of issue #2 though and it still needs to be done manually in your delegate. When working with WinForms I often use the following overload to remove that manual check as well.

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.
  }
}

Note

As Hans noted ObjectDisposedException is not the only exception that can be raised from the Invoke method. There are several others, including at least InvalidOperationException that you need to consider handling.

忘东忘西忘不掉你 2024-09-24 07:40:41

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

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

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

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

System.Timers.Timer is a horrible class. There is no good way to stop it reliably, there is always a race and you can't avoid it. The problem is that its Elapsed event gets raised from a threadpool thread. You cannot predict when that thread actually starts running. When you call the Stop() method, that thread may well have already been added to the thread pool but didn't get around to running yet. It is subject to both the Windows thread scheduler and the threadpool scheduler.

You can't even reliably solve it by arbitrarily delaying the closing of the window. The threadpool scheduler can delay the running of a thread by up to 125 seconds in the most extreme cases. You'll reduce the likelihood of an exception by delaying the close by a couple of seconds, it won't be zero. Delaying the close for 2 minutes isn't realistic.

Just don't use it. Either use System.Threading.Timer and make it a one-shot timer that you restart in the event handler. Or use a System.Windows.Forms.Timer, it is synchronous.

A WF Timer should be your choice here because you use Control.Invoke(). The delegate target won't start running until your UI thread goes idle. The exact same behavior you'll get from a WF timer.

葮薆情 2024-09-24 07:40:41

创建两个名为“StopTimer”和“TimerStopped”的布尔值。将计时器的 AutoReset 属性设置为 false。然后将 Elapsed 方法格式化为以下内容:

TimerStopped = false;
Invoke((MethodInvoker)delegate {
    // Work to do here.
});
if (!StopTimer)
    timer.Start();
else
    TimerStopped = true;

这样您就可以防止竞争条件,检查计时器是否应该继续并报告方法何时到达结束。

现在按如下方式格式化您的 FormClosing 事件:

if (!TimerStopped)
{
    StopTimer = true;
    Thread waiter = new Thread(new ThreadStart(delegate {
        while (!TimerStopped) { }
        Invoke((MethodInvoker)delegate { Close(); });
    }));
    waiter.Start();
    e.Cancel = true;
}
else
    timer.Dispose();

如果计时器尚未停止,则会启动一个线程等待它完成,然后再次尝试关闭表单。

Create two booleans called 'StopTimer' and 'TimerStopped'. Set the timer's AutoReset property to false. Then format the Elapsed method to the following:

TimerStopped = false;
Invoke((MethodInvoker)delegate {
    // Work to do here.
});
if (!StopTimer)
    timer.Start();
else
    TimerStopped = true;

This way you are preventing a race condition, checking if the timer should continue and reporting when the method has reached its end.

Now format your FormClosing event as follows:

if (!TimerStopped)
{
    StopTimer = true;
    Thread waiter = new Thread(new ThreadStart(delegate {
        while (!TimerStopped) { }
        Invoke((MethodInvoker)delegate { Close(); });
    }));
    waiter.Start();
    e.Cancel = true;
}
else
    timer.Dispose();

If the timer hasn't stopped yet, a thread is launched to wait until it has done so and then try to close the form again.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文