为什么不能从后台工作者访问UI组件?
本文关键字:UI 组件 访问 工作者 不能 后台 为什么 | 更新日期: 2023-09-27 17:49:44
所有线程共享资源。这就是多线程操作的全部问题。
MSDN说:
你必须小心不要在你的DoWork事件>处理程序中操作任何用户界面对象。相反,通过ProgressChanged和RunWorkerCompleted事件与用户界面通信。
BackgroundWorker事件不会跨AppDomain边界封送。不要在多个AppDomain中使用BackgroundWorker组件来执行多线程操作。
然而,当我使用backgroundworker时,并不是说我需要小心不要操纵任何UI对象,而是如果我试图从DOWork事件访问UI组件,那就不能。代码编译,但是当DoWork的代码运行时,我得到一个错误:
跨线程操作无效:控制'utAlerts'从创建它的线程以外的线程访问。
MSDN没有说明这是如何完成的或为什么。后台工作人员是否装饰了一些属性来防止这种情况?这是如何实现的呢?
如果你的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;
}
}
}