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

发布于 2024-10-31 09:27:17 字数 631 浏览 2 评论 0原文

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

MSDN 说:

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

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

然而,当我使用后台工作程序时,并不是说我需要小心不要操作任何 UI 对象,而是如果我尝试从 DOWork 事件访问 UI 组件,就不能这样做。代码可以编译,但是当 DoWork 的代码运行时,出现错误:

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

MSDN 没有说明这是如何完成的或为什么。后台工作者是否用某些属性装饰来防止这种情况发生?这是如何实现的?

Threads all share resources. That's the whole problem around multi-threaded operations.

MSDN says:

You must be careful not to manipulate any user-interface objects in your DoWork event >handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.

BackgroundWorker events are not marshaled across AppDomain boundaries. Do not use a BackgroundWorker component to perform multithreaded operations in more than one AppDomain.

And yet when I use the backgroundworker, it's not that I need to be careful not to manipulate any UI objects, it's that can't_ if I try to access the UI components from the DOWork event. The code compiles, but when the code for the DoWork runs, I get an error:

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

MSDN doesn't say anything about how is this done or why. Is the backgroundworker decorated with some attribute that prevents this? How is this accomplished?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

忆沫 2024-11-07 09:27:17

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

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

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


编辑:

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

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

至于它是如何工作的 - 这取决于您使用的框架。例如,在 Windows 窗体中,每个 Control (它是所有 UI 元素的基类)都有一个 Handle,并且 Handle 在内部是一个本机窗口句柄。此句柄用于检查窗口的线程 ID< /a> 针对当前线程 ID。这允许在不存储额外变量的情况下进行检查。

If you're handler is an instance method in your UI class, you should have access to the members of that class.

it's that my app won't even compile if I try to access the UI components from the DOWork event.

This will only happen if your DoWork handler is static or in a different class than the UI components. In that case, you may not have access to them, as they are not visible to you.


Edit:

BackgroundWorker is intended to do "work" that is unrelated to your User Interface. You cannot change User Interface elements on any thread other than the UI thread, as user interface elements tend to have thread affinity. This actually has nothing to do with BackgroundWorker, but rather threading and user interface elements.

BW is intended to work around this by giving you progress and completion events that are automatically marshalled back onto the UI thread for you, allowing you to change UI elements there. However, you can always do this directly yourself via Control.Invoke in Windows Forms or Dispatcher.Invoke in WPF.

As to how this works - It depends on what framework you're using. For example, in Windows Forms, every Control (which is the base class of all of the UI elements) has a Handle, and the Handle is internally a native window handle. This handle is used to check the window's Thread ID against the current thread ID. This allows the check to be made without storing extra variables.

浅紫色的梦幻 2024-11-07 09:27:17

当您尝试使用 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 后,此代码执行时不会出现错误。

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

The error that you get when you try to change/update UI controls with a BackgroundWorker has nothing to do with sharing resources over the thread. It simply states that you cannot alter a control that was created on another thread.

  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.

This is used so that multiple threads are not accessing/changing the same controls at the same time. BackgroundWorkers are Asynchronous and could cause a lot of problems if controls were updated while the main thread updating them as well.

I do not know how they achieved this, however, it is probably in the best interest that they prevented this from happening.

The MSDN provided another line of documentation to the segment you copied which states "BackgroundWorker events are not marshaled across AppDomain boundaries. Do not use a BackgroundWorker component to perform multithreaded operations in more than one AppDomain."

EDIT CORRESPONDES TO CONVERSATION IN COMMENTS:

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

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

With the addition of the CheckForIllegalCrossThreadCalls = false, this code executes without error.

In the summary of the boolean property, it states that it indicates whether to "catch calls on the wrong thread."

挽手叙旧 2024-11-07 09:27:17

它可以通过简单的事情来完成,例如让每个控件将当前线程(或者可能只是其 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;
        }
    }
}

It can be done with something as simple as having each control store the current thread (or maybe just its ID) in a private field in the constructor and then checking if the current thread is still that one before every method. Something like this:

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;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文