I have a project coded in .NET Winforms. I need to implement a data-mining operation, print the text to TextBox and update the progress.
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)
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. And 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.
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,
The Layout of my Form:
Form - 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.
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?
use this code iw will work
This is the problem:
You're trying to change the label text within the
BackgroundWorker
, which is not running on a UI thread. The point ofBackgroundWorker
is to do all the non-UI work there, usingReportProgress
to periodically "go back" to the UI thread and update the UI with the progress you're making.So either you need to change
label1.Text
inBwProgressChanged
as well, or you need to useControl.Invoke
/BeginInvoke
just as you would from any other background thread:For more about the copying part, see Eric Lippert's blog post, "Closing over the loop variable considered harmful". In this particular case it's only an issue because I'm using
BeginInvoke
. This could be changed to just:... 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. I generally prefer
BeginInvoke
overInvoke
.You're accessing your label - which has been created on the GUI thread - from within your backgroundworker-thread. Accessing a Windows control from a thread other than on which the control has been created, is not allowed; 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.
You must execute the methods of a control from the thread that has created them. To update them from a different thread use Invoke. An example of how this can be done can be found here. You should also read Multithreading with Forms and Controls on MSDN.