如何使用BackgroundWorker确保UI响应

时间:2010-01-18 09:47:12

标签: c# winforms backgroundworker

背景工作者是否在c#线程安全?

我问这个的原因是因为我得到了

  

在一个线程上创建的控件不能   成为控制权的父母   不同的线程

异常。这是我的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 个答案:

答案 0 :(得分:5)

这不是线程的问题,而是它试图在UI控件上调用方法。在WPF和WinForms中,控件只能在UI线程上调用(通常有一个)。你没有说你正在使用哪个,但是你需要为WinFms调用Control.Invoke方法,或者为WPF调用Dispatcher.Invoke

您显示的Invoke()反射方法实际上会调用当前线程上的方法。

答案 1 :(得分:2)

你可以像Mehrdad Afshari建议的那样调用,或者你可以利用在UI线程上返回的bgw的progress事件。或者工作完成事件也会返回UI线程。两者之间的区别是WorkCompleted最后只触发一次。您从DoWork获得了进展。

答案 2 :(得分:1)

虽然我不清楚你对BackgroundWorker的线程安全意味着什么,问题不在于那个对象; Windows窗体控件旨在在单个线程(UI线程)上进行操作。您不应该在不同的线程上操作Windows窗体对象。您可以使用Control.Invoke方法从其他线程调用UI线程中的操作(您当前使用的Invoke方法是由反射提供的,与此问题完全无关):

Invoke(new Action(MethodToRunInUIThread));

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

顺便说一句,如果您正在操作UI对象,那么使用后台工作程序是没有意义的。

答案 3 :(得分:1)

如果UI Control的功能需要很长时间才能执行,那么您可能无法做多少事情。在UI线程上发生长时间运行操作时会发生“冻结”,并且如果控件的该函数没有特别设置为线程安全的,则必须在主线程上运行。

通常,您希望将“文档”功能与显示它的控件分开。这样,您的文档可以加载到一个单独的独立线程上,并在准备好后再显示。否则,控件本身必须实现多线程加载例程以减缓加载冻结。

由于您在评论中指明这是第三方控件,因此您可能会在运气不佳。

答案 4 :(得分:0)

BackgroundWorker是一个基于线程的结构。线程安全问题与执行同时任务时的功能有关。也许你要求的是winforms控件,它通过一个独特的线程访问,即用户界面线程。

答案 5 :(得分:0)

@Graviton,找到答案的相关任务here。此人正在使用BackgroundWorker更新文本框,适用相同的概念(您的只是一个工作线程)。

答案 6 :(得分:0)

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

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

通常在Windows窗体中,最好尽可能使用Control.BeginInvoke()来帮助避免在一个线程等待另一个线程时可能发生的线程之间的死锁,就像使用Control.Invoke()一样。

如果“document”对象继承自System.Windows.Forms.Control,则只需调用document.BeginInvoke(myDelegate)即可。

但是,如果它实际上是一个封装GUI控件的其他组件,它可能会暴露一些调用BeginInvoke的方法。检查文档(如果有)。如果没有这样的能力,那么很遗憾它可能不是为了支持多线程应用而设计的。

看起来你对各种Invoke / BeginInvoke类型感到困惑(可以理解)。这个早期的问题:What is the difference between Invoke and BeginInvoke?和Jon Skeets的回答应该有助于澄清事情。