5
votes

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?

4

4 Answers

12
votes

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. 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.

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:

// 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". In this particular case it's only an issue because I'm using 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. I generally prefer BeginInvoke over Invoke.

5
votes

use this code iw will work

 BeginInvoke((MethodInvoker)delegate
               {
                   TextBox1.Text += "your text here";

               });
0
votes

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.

0
votes

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.