如何使用BackgroundWorker确保UI响应性

5
在C#中,BackgroundWorker线程安全吗?
我之所以问这个问题是因为我遇到了一个异常:
“在一个线程上创建的控件不能作为另一个线程上控件的父级。”
这是我的DoWork事件代码:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{



    var openFile = document.Open(MyFileName);
    e.Result = openFile;
}

document是在父表单创建时初始化的UI控件。在Open方法期间,document中的各种属性将被填充。

我尝试更改调用的代码,但仍然存在相同的问题。即,

document.GetType().GetMethod("Open)".Invoke(document, new object[]{MyFileName})

将产生与上述相同的错误。

有什么办法可以操作document控制?换句话说,如何使上面的代码工作?

编辑:有人建议我使用Control.Invoke,但它仍然无效(两个线程都挂起)。这是我尝试过的代码:

private delegate bool OpenFile(string filePath);
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{



    OpenFile oF = new OpenFile(document.Open);
    var openFile = Invoke(oF, MyFileName);  // it doesn't really matter whether I use BeginInvoke or Invoke, or other Control.Invoke, the end result is the same. Both the main thread hosting the document and the thread that launches the UI hanged.

    e.Result = openFile;
}
7个回答

5
问题不在于线程本身,而是它试图在UI控件上调用方法。在WPF和WinForms中,控件只能在UI线程上调用(通常只有一个)。您没有说明使用的是哪个,但需要调用WinForms的Control.Invoke方法或WPF的Dispatcher.Invoke方法。
您展示的Invoke()反射方法实际上会在当前线程上调用该方法。

我在我的DoWork事件中尝试了这段代码,但它仍然无法工作:this.Invoke(document.Open) - Graviton
那么它就不是一个控件。你能告诉我们 document 是什么吗? - GraemeF
这只是第三方库中的普通类,抱歉造成了混淆。 - Graviton
1
好的,如果那不是一个控件,你可以在另一个控件或窗口上调用Invoke方法,它将产生相同的效果;该方法将在UI线程上被调用。换句话说,你需要与UI相关的某些东西才能进行调用。 - GraemeF
在这里使用 Control.Invoke() 将完全背离使用 BackgroundWorker 的初衷。BackgroundWorker的目的是通过将长时间运行的任务移动到不同的线程来使UI保持响应性。在BackgroundWorker中使用 Invoke() 会启动一个新的线程,该线程立即调用您试图避免的线程。如果 Invoke() 是更长过程中的一部分,那么这样做就可以,但是在这里它是整个过程。虽然我怀疑OP是否混淆了文档是不是控件。我们真正需要知道 document 对象的确切类型。 - Joel Coehoorn
显示剩余2条评论

2

你可以像Mehrdad Afshari建议的那样调用,也可以利用bgw的进度事件,在UI线程上返回。或者工作完成事件也在UI线程上返回。两者之间的区别在于WorkCompleted仅在结束时触发一次,而Progress是由DoWork从你那里触发的。


“如何利用 bgw 的进度事件并在 UI 线程上返回来解决我的问题,即如何使 UI 有响应性?” - Graviton
2
也许这是对BGW功能的误解。如果您正在大量操纵UI控件,导致UI出现白屏或看似卡死,那么bgw是无法帮助您的!但是,如果您正在做其他事情(而不是触摸ui控件),例如访问缓慢的Web服务或数据库,则在bgw上执行此操作将使您的UI保持响应性。例如,您可以执行密集的操作x,然后使用进度事件向UI报告操作x已完成,这是中间结果,接下来执行操作y。 - Noel Kennedy

1

虽然我不确定您所说的BackgroundWorker的线程安全性确切含义是什么,但问题并不在于该对象;Windows Forms控件被设计为在单个线程(UI线程)上进行操作。您不应该在不同的线程上操纵Windows Forms对象。您可以通过使用Control.Invoke方法在其他线程中调用UI线程中的操作(您当前正在使用的Invoke方法是由反射提供的,与此问题完全无关):

Invoke(new Action(MethodToRunInUIThread));

void MethodToRunInUIThread() {
    // do stuff here.
}

顺便说一下,如果你所做的只是操作UI对象,使用后台工作线程就没有意义了。

@Mehdrad,我需要一个BackgroundWorker来保持UI的响应性,这也是它有意义的原因。 - Graviton
@Ngu Soon Hui:当然,如果您有一个长时间运行的任务不仅仅是UI操作,那么您将使用它。如果在“DoWork”方法中您所做的全部工作都是操纵UI对象,那么您不应该使用“BackgroundWorker”,原因是最终您将不得不在UI线程上执行所有这些UI操作。 - Mehrdad Afshari
它可能是有意义的,也可能更微妙。该线程只需通知“UI线程”发生了“新事件”,然后由UI拥有的任何通知处理程序来更新UI(如果需要)。 - Johann Gerell
@Johann:我知道。在这种情况下,如果你所做的只是UI操作,那么就会是错误的,你知道的。我之所以这样说,是因为显然OP发布的代码只是在控件上调用一个方法,这应该使用Invoke来完成,而Invoke只是在UI线程上运行方法,所以在这里使用BackgroundWorker是没有意义的。 - Mehrdad Afshari

1
如果UI控件的功能执行时间很长,可能没有太多可以做的。当UI线程上发生长时间运行的操作时,就会出现“冻结”,如果控件的该函数没有特别制作成线程安全的,则必须在主线程上运行。
通常,您希望将“文档”功能与显示它的控件分离开来。这样,您的文档可以在单独的、独立的线程上加载,并在准备好后再显示。否则,控件本身就必须实现一个多线程加载例程来减缓加载冻结。
由于您在评论中指定了这是第三方控件,因此您可能没有什么运气。

0

BackgroundWorker 是一个基于线程的结构。线程安全问题涉及到同时执行任务时的函数。也许你所询问的是关于通过唯一线程即用户界面线程访问的 winforms 控件。


0

@Graviton,发现一个相关的任务和答案在这里。该人正在使用BackgroundWorker来更新文本框,同样的概念适用于你的单个工作线程。


是的,当然可以。请看我的更新问题。我发现在应用您链接中指定的技术后,两个线程都挂起了。 - Graviton
似乎您没有像示例中展示的那样进行InvokeRequired检查并进行BeginInvoke()调用。InvokeRequired确保您处于正确的线程中,而使用BeginInvoke()呼叫基本上是在当前方法中进行回呼。我了解到文档并未提供Invoke方法,因此请使用窗体对象(当您在第二个代码示例中调用Invoke()时可能正在这样做)。只要记住,调用BeginInvoke()是一回事......该方法需要继续调用BeginInvoke()直到InvokeRequired为false。 - Jamie Altizer

0

您需要在DoWork中使用Control.BeginInvoke()。这将异步执行委托,确保调用线程不会“挂起”。

Control.Invoke()也会在另一个线程上执行委托,但会导致调用线程等待其完成。

通常,在Windows表单中,尽可能使用Control.BeginInvoke()有助于避免线程之间发生死锁,这种死锁可能会在一个线程等待另一个线程时发生,就像使用Control.Invoke()一样。

如果“document”对象继承自System.Windows.Forms.Control,则可以简单地调用document.BeginInvoke(myDelegate)。

但是,如果它实际上是封装GUI控件的其他组件,则可能会公开某种调用BeginInvoke的方式。请检查文档(如果有)。如果没有这样的能力,那么恐怕它只是不支持多线程应用程序的设计。

看起来您对各种Invoke / BeginInvoke类型感到困惑(可以理解)。这个早期的问题:Invoke和BeginInvoke之间的区别是什么?和Jon Skeet的回答应该有助于澄清事情。


我明白,但是当我调用 BeginInvoke 时,托管 document 的线程和另一个启动移动对话框的线程都挂起了。 - Graviton

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