【问题标题】:What happens with a Form when a ThreadPool WaitCallback calls it after it has been closed?当 ThreadPool WaitCallback 在关闭后调用 Form 时,会发生什么情况?
【发布时间】:2012-03-05 05:29:19
【问题描述】:

假设在 Windows 窗体中,我启动了一个长时间运行的任务,如下所示:

ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateDailyTasksChart));

然后,这个函数运行了一段时间,但我在它完成之前关闭了窗口。

private void UpdateDailyTasksChart (object param)
{
  //Call Data Access Layer, go get MySQL Data. But I close the Form.
  UICallback(chartData); //The Form is closed when this is executed.
}

现在这就是 UICallback 的作用:

private delegate void UICallbackDel(object chartData);
private void UICallback (object chartData)
{
  if (InvokeRequired)
  {
    this.Invoke(new UICallbackDel(UICallback), chartData);
  }
  else
  {
    aButtonOnMyForm.Visible = false; //But the Form has been closed!
  }
}

奇怪的是,这段代码并没有崩溃。

我在 Form_Closed 事件中放置了断点,它确实执行了。我还没有检查表单是否仍然存在,例如,用类变量声明它。但我的猜测是确实如此。

所以问题是:GC 只会在我的线程完成时收集表单?或者发生了什么?

【问题讨论】:

  • 表单是否已明确处理?
  • 它是偶然工作的。你很幸运,当表单被处理时, Visible 属性已经是假的,所以实际上没有做任何事情。它不排除炸弹每月一次线程竞赛的可能性。
  • @Hans Passant:但是如果 Form 已经被释放,仅仅访问 Visible 属性就会崩溃,不是吗?无论如何,.Visible 行只是其中之一。我在那里执行了一些其他功能。当线程完成执行并且这些行都没有崩溃时,会对表单进行一些更改。但是由于我没有明确地处理表单,我猜,正如 Phong 所说,GC 还没有完成它的工作,所以表单仍然存在,等待线程结束。这很烦人。我将不得不放弃 ThreadPool 并使用允许更多控制的 Thread 对象。
  • 它不能被垃圾收集,您保留对允许您调用实例方法的表单(某处)的引用。这本身就是一个问题,它是一个泄漏。也不小,Form 类对象很大。处理这个问题的唯一明智的方法是实际阻止表单关闭,直到您知道线程不能再调用。此答案涵盖:stackoverflow.com/questions/1731384/…
  • @Hans Passant:我希望能够关闭表单并完全中止线程。尽管我想知道如何在不将任何对 Form 的引用传递给线程的情况下完成此操作,这将阻止它被 GC-ed。也许在 Closing 事件中中止线程会起作用?

标签: c# winforms multithreading


【解决方案1】:

这里有两点:

  1. 如果您明确处置表单,您可能希望在工作项委托执行时引发ObjectDisposedException
  2. 您的工作项委托通过thisInvokeRequired(反过来又引用this)和aButtonOnMyForm 持有对表单的引用。因此,只要ThreadPool 线程尚未完成执行,您的表单就没有资格进行垃圾回收。这就是代码不会崩溃的原因。 (注意:this 始终可以隐式访问。)

由此得出的另一件事是,在表单关闭之前从表单中注销外部事件处理程序通常是明智的,以免造成内存泄漏。如果你注册了一个 lambda 表达式或匿名委托,那么你需要保存对它的引用以便以后取消注册。简单地复制和粘贴相同的代码是行不通的。

【讨论】:

  • 谢谢。我想我应该使用 Thread 对象或 BackgroundWorker。无论如何,当窗口关闭时我可以中止一些事情。
【解决方案2】:

您可能想看看this 的帖子。

基本上,您可能会或可能不会收到ObjectDisposedException,这取决于您尝试在表单上调用时 GC 正在做什么。您可以尝试捕获它,但无法对其进行可靠测试,因此这更像是一种 hack。

【讨论】:

  • 这没有任何意义。如果您仍然有对表单的引用,很明显 GC 会对它做什么:什么都没有。
【解决方案3】:

垃圾收集器并不关心对象是“关闭”、“处置”或类似的。它只关心对象是否仍然可以访问。在您的情况下,仍然可以使用this(隐式或显式)访问该表单。这就是您的应用程序不会崩溃的原因。

当然,如果一个对象被关闭或处置或类似的东西,它有权抛出ObjectDisposedException,或者你调用它的任何方法类似。但它当然不必那样做。

【讨论】:

  • 由于我不喜欢显式处理,我可能会通过使用“可中止”线程来解决这个问题。
  • 只要你不是要打电话给Thread.Abort(),那么我想应该可以。
  • 这正是我的意思。为什么不那么好?对我来说,允许用户关闭窗口比线程完成更重要,因为它是一个图表:即不是关键操作。如果用户关闭窗口,那么我就放弃一切,仅此而已。
  • Because Thread.Abort() can easily leave your application in an inconsistent state. 您可以设置一个标志,告诉您不要调用表单上的操作,而不是调用该方法。
  • 但是即使在表单关闭后,这也会让线程继续工作。更重要的是,在线程完成之前不会对表单进行 GC,不是吗?
猜你喜欢
  • 2013-11-08
  • 1970-01-01
  • 2011-02-02
  • 1970-01-01
  • 1970-01-01
  • 2020-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多