首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >对象处理异常和多线程应用程序

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

Stack Overflow用户
提问于 2010-09-04 14:21:59
回答 3查看 6.8K关注 0票数 5

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

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

代码语言:javascript
复制
this.Invoke((MethodInvoker)delegate()
  {
       label1.Text = "Example";
  });

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

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-09-05 00:18:13

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

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

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

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

票数 3
EN

Stack Overflow用户

发布于 2010-09-04 14:29:52

这是一个有点棘手的命题,因为您必须在给定的关闭事件上确保以下内容

  1. 计时器已停止。这是相当直接的
  2. ,当委托运行时,正在更新的控件不会被释放。
  3. 当前运行的代码已经完成计时器滴答。这很难,但可行的
  4. 没有挂起的调用方法。这是一个相当难完成的

我以前遇到过这个问题,我发现预防这个问题是非常有问题的,并且涉及到很多混乱的、难以维护的代码。相反,捕获这种情况下可能出现的异常要容易得多。通常,我通过包装Invoke方法来实现,如下所示

代码语言:javascript
复制
static void Invoke(ISynchronizedInvoke invoke, MethodInvoker del) {
  try {
    invoke.Invoke(del,null);
  } catch ( ObjectDisposedException ) {
    // Ignore.  Control is disposed cannot update the UI.
  }
}

如果您对此异常的后果感到满意,那么忽略此异常本身并没有错。这是如果你习惯于UI在它已经被释放之后没有更新的话。我当然是:)

上面的代码并没有处理第二个问题,它仍然需要在你的委托中手动完成。在使用WinForms时,我也经常使用以下重载来删除手动检查。

代码语言:javascript
复制
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所指出的,ObjectDisposedException并不是唯一可以从Invoke方法中引发的异常。您还需要考虑处理其他几个问题,至少包括InvalidOperationException

票数 7
EN

Stack Overflow用户

发布于 2013-09-06 04:44:17

创建两个布尔值'StopTimer‘和'TimerStopped’。将计时器的AutoReset属性设置为false。然后将经过的方法格式化为以下格式:

代码语言:javascript
复制
TimerStopped = false;
Invoke((MethodInvoker)delegate {
    // Work to do here.
});
if (!StopTimer)
    timer.Start();
else
    TimerStopped = true;

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

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

代码语言:javascript
复制
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();

如果计时器还没有停止,就会启动一个线程,等待它停止计时器,然后再次尝试关闭窗体。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3641147

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档