为什么后台工作者可以修改 UI 组件

本文关键字:修改 UI 组件 后台 工作者 为什么 | 更新日期: 2023-09-27 18:19:31

我正在更改BackgroundWorker按钮的Text,它可以工作。我认为这应该抛出一个例外。为什么不呢?

为什么我没有得到Cross-thread operation not valid: ... accessed from a thread other than the thread it was created on.

编辑:谢谢大家。

也许原因是 UI 上有:Thread.Sleep(1000);

public Form1()
{
    InitializeComponent();
    backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
    backgroundWorker1.RunWorkerAsync();
    Thread.Sleep(1000);
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    button1.Text = "a";
}

但是,我注意到以下代码也运行良好,尽管(间接(影响了 UI。

public partial class Form1 : Form
{
    int i;
    public Form1()
    {
        InitializeComponent();
        i = 1;
        backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
        backgroundWorker1.RunWorkerAsync();
        for (int j = 0; j < 100000000; j++) ;
        button1.Text = i.ToString();
    }
    void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        i = 2;
    }
}

为什么?

为什么后台工作者可以修改 UI 组件

工作

完成后后台工作线程调用的回调函数 ( BackgroundWorker.RunWorkerCompleted ( 为了您的方便,已经使用 UI 线程调度程序。

编辑:

@ispiro:不能保证第二个示例中的代码始终有效 - 您仍然有变量i的跨线程更新,因此您应该volatile声明它以确保它始终正确更新。

第一个代码不起作用的原因是 .NET 框架可帮助您检测此跨线程访问。正如@Greg D指出的那样,这可以被禁用(这绝对是一个禁忌(。有关详细信息,请查看此 MSDN 页面:

.NET Framework 可帮助您检测何时访问 以非线程安全的方式进行控件。跑步时 调试器中的应用程序,以及该线程以外的线程 创建控件的控件尝试调用该控件,调试器 引发 InvalidOperationException,并显示消息"控制控制 从创建它的线程以外的线程访问的名称。

此异常在调试期间可靠地发生,并且在某些 情况,在运行时。当您 调试在 .NET Framework 之前编写的应用程序 .NET Framework 2.0 版。强烈建议您解决此问题 看到它时有问题,但您可以通过设置 CheckForIllegalCrossThreadCalls 属性为 false。这会导致您的 控制运行,就像在 Visual Studio .NET 2003 和 .NET Framework 1.1.

因为为了方便起见,RunWorkerCompleted事件的回调函数是在 UI 线程上调用(调用(的。 请注意,尽管并非每个事件都是如此,但显然DoWork回调在单独的线程上运行。

有几种可能性:

1( 您的程序已禁用跨线程检查。 这是软件在人们不了解 UI 线程周围的线程规则时使用的一种可悲的常见黑客。

2( 程序正在通过后台工作者的进度或已完成事件修改 UI。 这些事件被封送到创建后台工作线程的线程的同步上下文中。 如果 BackgroundWorker 在其经典上下文中用作 WinForms 设计器组件,那么您就是黄金。 这些事件将为您编组到 UI 线程中,因此您不必自己做这种废话。

这个问题

有点误导,因为BackgroundWorder只提供某些保证。以下是BGW文档中的"注释":

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

RunWorkerCompletedProgressChanged 事件将发布到创建 BGW* 的线程。但是,对于DoWork事件,相同的保证不成立不要从其中访问 UI,:)

本质上,RunWorkerCompletedProgressChanged中的代码有效地自动包装在Control.BeginInvoke中,它"发布消息以调用回调"到窗口/线程的调度队列。

快乐编码。


*由于创建(或者可能是启动的?(BGW 的线程会影响发布回调的位置,因此可以创建不会在所需 UI 线程上"运行"的 BGW。若要避免这种奇怪的行为,请始终在应回发到的 UI 线程上创建/启动 BGW。

答案可能是可以执行跨线程操作。他们只是容易遇到麻烦。而Control(应该抛出异常(在Sleep时可能不会"介意"交叉线程。