关闭窗体时出现异常(线程+调用)

6

我刚开始学习c#中的线程和方法调用,但是我遇到了一个问题,无法找到解决方案。

我创建了一个基本的C#窗体程序,通过启动线程和调用委托来不断更新并显示一个数字。

在Form1_load上启动新线程:

private void Form1_Load(object sender, EventArgs e)
  {
        t = new System.Threading.Thread(DoThisAllTheTime);
        t.Start();
  }

公共的 void DoThisAllTheTime(不断更新数字):

public void DoThisAllTheTime()
  {
     while(true)
      {
        if (!this.IsDisposed)
         {
           number += 1;
           MethodInvoker yolo = delegate() { label1.Text = number.ToString(); };
           this.Invoke(yolo);
         }
      }
  }

现在当我点击表单的X按钮时,我会得到以下异常信息:
“未处理的类型为'System.ObjectDisposedException'的异常在System.Windows.Forms.dll中发生”
“无法更新已删除的对象”
尽管我确实检查了表单是否被处理掉了。
编辑:我在代码中添加了catch (ObjectDisposedException ex),这解决了问题。现在代码可以正常工作了。
  public void DoThisAllTheTime()
  {
     while(true)
      {
         number += 1;

         try {  
              MethodInvoker yolo = delegate() { label1.Text = number.ToString(); };
              this.Invoke(yolo);
             }
         catch (ObjectDisposedException ex)
             {
              t.Abort();
             }
      }
 }

4
好的,原文是这样的:“Well, the form got disposed after (!this.IsDisposed) was calculated, but before this.Invoke(yolo); was called. Welcome to the world of races.”翻译:在计算了(!this.IsDisposed)之后,但在调用this.Invoke(yolo);之前,窗体被释放了。欢迎来到竞争的世界。 - Joker_vD
我已经编辑了你的标题。请参考“问题的标题应该包含“标签”吗?”,在那里达成共识是“不应该”。 - John Saunders
@Joker_vD 我该如何修复它?我以为检查会比方法调用更快。MSDN上也是这样使用的。(http://msdn.microsoft.com/en-us/library/system.windows.forms.methodinvoker(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2) - John
正如其他人已经说过的,这是一个竞争问题,请参见:https://dev59.com/PXI-5IYBdhLWcg3wZ3cR - Alireza
@Bart 不要指望 MSDN 的示例代码是正确的。通常情况下,它并不正确。 - oefe
3个回答

4
您对 this.IsDisposed 的调用始终过时。 您需要拦截表单关闭事件并明确停止线程。 然后您就不必进行那个IsDisposed测试了。
有许多方法可以做到这一点。 就我个人而言,我会使用 System.Threading.Tasks 命名空间,但如果您想保持使用 System.Threading,则应定义一个成员变量 _updateThread,并在加载事件中启动它:
_updateThread = new System.Threading.Thread(DoThisAllTheTime);
_updateThread.Start();

在你的关闭事件中:
private void Form1_Closing(object sender, CancelEventArgs e)
{
    _stopCounting = true;
    _updateThread.Join();
}

最后,将IsDisposed的测试替换为对您新的_stopCounting成员变量值的检查:

public void DoThisAllTheTime()
{
    MethodInvoker yolo = delegate() { label1.Text = number.ToString(); };
    while(!_stopCounting)
    {
        number += 1;
        this.Invoke(yolo);
    }
}

我尝试在Form1_closing中添加“t.abort();”,但这也没有帮助。 - John
@Bart:尝试设置一个标志,然后调用t.Join();来等待线程识别它。 - Ry-
@Bart:正如oefe所说,那太可怕了!:D - Ry-
实际上,在循环的每个阶段都不需要定义一个新的委托 - 这会使您的垃圾收集器工作得比必要的更加艰难。相反,将一个方法添加到您的类中并调用它即可。 - Rob Lyndon
2
由于Invoke会阻塞等待主线程,如果在Form1_Closing方法执行到number += 1;这一行时可能会出现死锁的情况。随着在该行执行更多的工作,死锁的几率会增加。我通过使用BeginInvoke而不是Invoke来解决了我的应用程序中的这个问题,因为BeginInvoke不会阻止线程退出,因此Join不会永远阻塞。 - Wayne Uroda
显示剩余3条评论

2

只需要将这个覆盖重写放在你的表单类中:

protected override void OnClosing(CancelEventArgs e) {
    t.Abort();
    base.OnClosing(e);
}

对于一个好的解决方案点个赞。获取更多信息请参见https://dev59.com/PXI-5IYBdhLWcg3wZ3cR - Alireza
@阿里雷扎 谢谢伙计。这个答案对于我从问题中获得的 -且想到的- 初学者技能来说是兼容的。不过还是谢谢你。干杯 - amiry jd
2
正常情况下的关闭操作不得包含对 Thread.Abort()Application.Exit() 等方法的调用。我曾经见过由此引起的一个 bug,因为在 OnClose() 事件处理程序中存在一个虚假的 Environment.Exit(),导致事务未能回滚。 - Joker_vD

-3
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Thread.CurrentThread.Abort();
}

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