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

本文关键字:UI 组件 访问 工作者 不能 后台 为什么 | 更新日期: 2023-09-27 17:49:44

所有线程共享资源。这就是多线程操作的全部问题。

MSDN说:

你必须小心不要在你的DoWork事件>处理程序中操作任何用户界面对象。相反,通过ProgressChanged和RunWorkerCompleted事件与用户界面通信。

BackgroundWorker事件不会跨AppDomain边界封送。不要在多个AppDomain中使用BackgroundWorker组件来执行多线程操作。

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

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

MSDN没有说明这是如何完成的或为什么。后台工作人员是否装饰了一些属性来防止这种情况?这是如何实现的呢?

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

如果你的handler是你的UI类中的一个实例方法,你应该有权访问这个类的成员。

如果我试图从DOWork事件访问UI组件,我的应用程序甚至不会编译。

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


编辑:

BackgroundWorker旨在做与用户界面无关的"工作"。除了UI线程之外,您不能在任何线程上更改User Interface元素,因为用户界面元素往往具有线程亲和性。这实际上与BackgroundWorker无关,而是与线程和用户界面元素有关。

BW旨在通过给你进程和完成事件来解决这个问题,这些事件会自动编组回UI线程,允许你在那里更改UI元素。但是,您可以通过Windows窗体中的Control.Invoke或WPF中的Dispatcher.Invoke直接完成此操作。

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

当你尝试用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边界封送。"不要在多个AppDomain中使用BackgroundWorker组件来执行多线程操作。"

编辑对应于评论中的对话:

    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属性的摘要中,它指出它指示是否"在错误的线程上捕获调用"。

可以简单地让每个控件在构造函数的私有字段中存储当前线程(或者可能只是它的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;
        }
    }
}