为什么后台工作者可以修改 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;
}
}
为什么?
完成后后台工作线程调用的回调函数 ( 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 事件与用户界面通信。
RunWorkerCompleted
和 ProgressChanged
事件将发布到创建 BGW* 的线程。但是,对于DoWork
事件,相同的保证不成立:不要从其中访问 UI,:)
本质上,RunWorkerCompleted
和ProgressChanged
中的代码有效地自动包装在Control.BeginInvoke
中,它"发布消息以调用回调"到窗口/线程的调度队列。
快乐编码。
*由于创建(或者可能是启动的?(BGW 的线程会影响发布回调的位置,因此可以创建不会在所需 UI 线程上"运行"的 BGW。若要避免这种奇怪的行为,请始终在应回发到的 UI 线程上创建/启动 BGW。
答案可能是可以执行跨线程操作。他们只是容易遇到麻烦。而Control
(应该抛出异常(在Sleep
时可能不会"介意"交叉线程。