【问题标题】:Why can't UI components be accessed from a backgroundworker?为什么不能从后台工作人员访问 UI 组件?
【发布时间】:2011-08-01 17:35:44
【问题描述】:

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

MSDN 说:

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

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

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

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

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

【问题讨论】:

  • @Martinho:对不起,它确实可以编译,但我得到了跨线程无效错误。查看我对问题的更改。我不确定为什么我认为它不会编译。很抱歉造成混乱。

标签: c# .net user-interface backgroundworker


【解决方案1】:

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

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

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


编辑:

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

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

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

【讨论】:

  • 对不起里德,我犯了一个错误。出于某种原因,我认为它不会编译,但确实如此,我只是得到了跨线程错误。
  • @Nick:我编辑了这个。 @Richard:请参阅我的编辑以获取有关为什么会发生这种情况的更多详细信息......它实际上与 BackgroundWorker 没有任何关系(直接)
  • 顺便说一句 - 好奇为什么投反对票?鉴于原始帖子,我发布的内容完全准确......
  • +1 表示它是防止跨线程更新的控件,而不是直接的 BW。
  • 感谢 Reed 的精彩回答。
【解决方案2】:

当您尝试使用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,这段代码执行没有错误。

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

【讨论】:

  • @Kyle:是的,这是我得到的错误,我想知道它是如何实现的。
  • @Richard DesLonde:我刚刚编辑了关于“AppDomains”的问题。因为 BackgroundWorker_DoWork 函数与创建 Form1 及其所有控件的线程不在同一个 AppDomain 中,所以您无法访问它们。
  • @Richard:来自 MSDN; “多个应用程序域可以在一个进程中运行;但是,应用程序域和线程之间没有一一对应的关系。多个线程可以属于一个应用程序域,而给定的线程并不局限于单个应用程序域。应用程序域,在任何给定时间,一个线程在单个应用程序域中执行。”
  • @Richard: Override the CrossThread 显然,它也可以被覆盖。
  • @Kyle:但是后台工作人员在不同的应用程序域中运行似乎很奇怪。不是吗?你能找到任何证据吗?
【解决方案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;
        }
    }
}

【讨论】:

  • 非常酷。那么你认为每个控件都这样做吗?这是某个接口还是每个控件都实现的东西?
  • @Richard:不需要接口。从我的示例实现中可以看出,跨线程预防机制没有任何公开的内容:检查和存储的 ID 都是私有的。我可以通过直接查看源来检查这一点,但我不再有 Reflector :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-07
相关资源
最近更新 更多