为什么不能从后台工作者访问UI组件?

时间:2011-04-08 18:16:59

标签: c# .net user-interface backgroundworker

线程全部共享资源。这是围绕多线程操作的整个问题。

MSDN说:

  

您必须小心不要操作DoWork事件>处理程序中的任何用户界面对象。而是通过ProgressChanged和RunWorkerCompleted事件与用户界面进行通信。

     

BackgroundWorker事件不是跨AppDomain边界编组的。不要使用BackgroundWorker组件在多个AppDomain中执行多线程操作。

然而,当我使用backgroundworker时,并不是我需要小心不要操纵任何UI对象,如果我尝试从DOWork事件中访问UI组件,那就不行了。代码编译,但是当DoWork的代码运行时,我收到一个错误:

  

跨线程操作无效:控制从创建它的线程以外的线程访问的'utAlerts'。

MSDN没有说明这是怎么做的或原因。 backgroundworker是否装饰了一些阻止这种情况的属性?这是如何完成的?

3 个答案:

答案 0 :(得分:6)

如果你的处理程序是你的UI类中的实例方法,你应该有权访问该类的成员。

  

如果我尝试从DOWork事件中访问UI组件,那么我的应用程序甚至无法编译。

只有当您的DoWork处理程序是静态的或与UI组件不同的类时,才会发生这种情况。在这种情况下,您可能无权访问它们,因为您无法看到它们。


编辑:

BackgroundWorker旨在执行与您的用户界面无关的“工作”。您不能在UI线程以外的任何线程上更改用户界面元素,因为用户界面元素往往具有线程关联性。这实际上与BackgroundWorker无关,而是与线程和用户界面元素无关。

BW旨在通过为您提供自动编组回UI线程的进度和完成事件来解决此问题,从而允许您更改UI元素。但是,您始终可以通过Windows窗体中的Control.Invoke或WPF中的Dispatcher.Invoke直接自行完成此操作。

至于它是如何工作的 - 这取决于你正在使用的框架。例如,在Windows窗体中,每个Control(所有UI元素的基类)都有一个Handle,而Handle在内部是一个本机窗口句柄。此句柄用于check the window's Thread ID对当前线程ID。这样就可以在不存储额外变量的情况下进行检查。

答案 1 :(得分:4)

尝试使用BackgroundWorker更改/更新UI控件时出现的错误与通过线程共享资源无关。它只是声明你不能改变在另一个线程上创建的控件。

  private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
  {   
        textBox1.Text = "Test";
  }

Results in:

Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.

这是为了使多个线程不同时访问/更改相同的控件。 BackgroundWorkers是异步的,如果在主线程更新控件时更新了控件,可能会导致很多问题。

我不知道他们是如何实现这一目标的,然而,他们阻止这种情况发生可能符合他们的最佳利益。

MSDN为您复制的段提供了另一行文档,其中指出“BackgroundWorker事件未在AppDomain边界编组。请勿使用BackgroundWorker组件在多个AppDomain中执行多线程操作。”

编辑评论中的对话:

    private void Form1_Load(object sender, EventArgs e)
    {
        TextBox.CheckForIllegalCrossThreadCalls = false;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {   
        textBox1.Text = "Test";
    }

通过添加CheckForIllegalCrossThreadCalls = false,此代码可以正常执行。

在boolean属性的摘要中,它声明它指示是否“捕获错误线程上的调用。”

答案 2 :(得分:3)

可以通过使每个控件将当前线程(或者可能只是其ID)存储在构造函数的私有字段中然后在每个方法之前检查当前线程是否仍然是那个来完成。像这样:

class ThreadAffineObject
{
    private readonly Thread originalThread;
    public ThreadAffineObject()
    {
        this.originalThread = Thread.CurrentThread;
    }

    private void PreventCrossThreadOperation()
    {
        if(Thread.CurrentThread != originalThread)
            throw new CrossThreadOperationException();
    }

    public void DoStuff()
    {
        PreventCrossThreadOperation();
        // Actually do stuff
    }

    private int someField;
    public int SomeProperty
    {
        get { return someField; } // here reading is allowed from other threads
        set
        {
            PreventCrossThreadOperation(); // but writing isn't
            someField = value;
        }
    }
}