[英].NET BackGroundWorker - InvalidOperationException : Cross-thread operation not valid
I have a project coded in .NET Winforms. 我有一个用.NET Winforms编码的项目。 I need to implement a data-mining operation, print the text to TextBox and update the progress. 我需要实现数据挖掘操作,将文本打印到TextBox并更新进度。
I tried to use BackgroundWorker to do, but it throws a InvalidOperationException ( Cross-thread operation not valid: Control 'xxxxx' accessed from a thread other than the thread it was created on ) 我尝试使用BackgroundWorker来做,但它抛出一个InvalidOperationException( 跨线程操作无效:控件'xxxxx'从一个线程访问,而不是它创建的线程 )
To narrow down the potential causes of the problem, I started a new project, included the following: Button - To start the BackgroundWorker Label - to print the text. 为了缩小问题的潜在原因,我开始了一个新项目,包括以下内容:按钮 - 启动BackgroundWorker标签 - 打印文本。 And ProgressBar. 和ProgressBar。
However, the result is the same. 但是,结果是一样的。 I searched on SOF, and was told to use a delegate, but I am not familiar with it. 我搜索了SOF,并被告知要使用代表,但我不熟悉它。
This is the code sample that throws the error: 这是抛出错误的代码示例:
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace TestProject
{
public partial class Form1 : Form
{
private readonly BackgroundWorker _bw = new BackgroundWorker();
public Form1()
{
InitializeComponent();
_bw.DoWork += RosterWork;
_bw.ProgressChanged += BwProgressChanged;
_bw.RunWorkerCompleted += BwRunWorkerCompleted;
_bw.WorkerReportsProgress = true;
_bw.WorkerSupportsCancellation = false;
}
private void RosterWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
for (int i = 0; i < 1000; i++)
{
label1.Text = i.ToString();
_bw.ReportProgress(Convert.ToInt32((i * (100 / 1000))));
}
}
private void BwProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void btnStart_Click(object sender, EventArgs e)
{
progressBar1.Show();
_bw.RunWorkerAsync();
}
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Hide();
}
}
}
Update: I follow Jon Skeet's answer, it really work on my test project, but back to my real project, 更新:我遵循Jon Skeet的回答,它确实适用于我的测试项目,但回到我的真实项目,
The Layout of my Form: 我的表格布局:
Form - TabControl - Tab1 -Tab1Panel -TextBox1 表单 - TabControl - Tab1 -Tab1Panel -TextBox1
When reach this line : 到达此行时:
TextBox txtbox1 = new TextBox();
Tab1Panel.Controls.Add(txtbox1);
The error still occur when i add Textbox to Panel Control Programmatically. 当我以编程方式将Textbox添加到Panel Control时,仍会出现错误。
Finally,I replace by this: 最后,我替换为:
if (Tab1Panel.InvokeRequired)
Tab1Panel.Invoke((MethodInvoker)delegate { Tab1Panel.Controls.Add(txtbox1); });
else
Tab1Panel.Controls.Add(txtbox1);
Everything is work. 一切都很有效。 How to determine the control is InvokeRequired, Is it control specified? 如何确定控件是InvokeRequired,是否指定了控件?
This is the problem: 这就是问题:
label1.Text = i.ToString();
You're trying to change the label text within the BackgroundWorker
, which is not running on a UI thread. 您正在尝试更改BackgroundWorker
的标签文本,该文本未在UI线程上运行。 The point of BackgroundWorker
is to do all the non-UI work there, using ReportProgress
to periodically "go back" to the UI thread and update the UI with the progress you're making. BackgroundWorker
是在那里完成所有非UI工作,使用ReportProgress
定期“返回”UI线程并使用您正在进行的进度更新UI。
So either you need to change label1.Text
in BwProgressChanged
as well, or you need to use Control.Invoke
/ BeginInvoke
just as you would from any other background thread: 因此,您需要更改BwProgressChanged
label1.Text
, 或者您需要像使用任何其他后台线程一样使用Control.Invoke
/ BeginInvoke
:
// Don't capture a loop variable in a lambda expression...
int copy = i;
Action updateLabel = () => label1.Text = copy.ToString();
label1.BeginInvoke(updateLabel);
For more about the copying part, see Eric Lippert's blog post, "Closing over the loop variable considered harmful" . 有关复制部分的更多信息,请参阅Eric Lippert的博客文章“关闭循环变量被认为有害” 。 In this particular case it's only an issue because I'm using BeginInvoke
. 在这种特殊情况下,它只是一个问题,因为我正在使用BeginInvoke
。 This could be changed to just: 这可以改为:
Action updateLabel = () => label1.Text = i.ToString();
label1.Invoke(updateLabel);
... but now the background worker would always be waiting for the UI to catch up before it kept going, which in real life is usually not what you want. ...但是现在后台工作人员总是会等待UI继续前进,这在现实生活中通常不是你想要的。 I generally prefer BeginInvoke
over Invoke
. 我通常更喜欢BeginInvoke
不是Invoke
。
use this code iw will work 使用这个代码我会工作
BeginInvoke((MethodInvoker)delegate
{
TextBox1.Text += "your text here";
});
You're accessing your label - which has been created on the GUI thread - from within your backgroundworker-thread. 您正在从后台工作者线程中访问您的标签 - 已在GUI线程上创建。 Accessing a Windows control from a thread other than on which the control has been created, is not allowed; 不允许从创建控件之外的线程访问Windows控件; therefore you're given the exception. 因此你得到了例外。
You should not access the label directly, but instead you should raise an event from within your thread, - and make sure it's invoked on the correct thread - that signals the UI so that you can change the content of the label. 您不应该直接访问标签,而应该从您的线程中引发一个事件,并确保在正确的线程上调用它 - 它向UI发出信号,以便您可以更改标签的内容。
You must execute the methods of a control from the thread that has created them. 您必须从创建它们的线程中执行控件的方法。 To update them from a different thread use Invoke . 要从其他线程更新它们,请使用Invoke 。 An example of how this can be done can be found here . 可以在此处找到如何完成此操作的示例。 You should also read Multithreading with Forms and Controls on MSDN. 您还应该在MSDN上阅读使用表单和控件的多线程 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.