Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET BackGroundWorker - InvalidOperationException : Cross-thread operation not valid

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?

like image 495
Cheung Avatar asked Nov 01 '11 06:11

Cheung


2 Answers

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.

like image 118
Jon Skeet Avatar answered Oct 04 '22 22:10

Jon Skeet


use this code iw will work

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

               });
like image 35
vzades Avatar answered Oct 04 '22 21:10

vzades