18
votes

I have a winform application (one form), on this form there is a RichTextBox. In the constructor of this form I create an instance of the class MyClass. In the “Form_Load” I call the method Initialisation from MyClass instance.

In the form constructor

myClass = new MyClass(RichTextBox richTextBox);

In the Form_Load

myClass.Initialisation();

In the Initialisation method, in a loop, I read some parmeters do other stuffs. To not freeze the application (because some operation can take a while, some seconds), I use a BackgroundWorker. I use it like this (see code below).

When I execute, I get this error : Cross-thread operation not valid: Control ‘richTextBox’ accessed from a thread other than the thread it was created on.

Could you tell me how solve this ? Work perfect when I don't access the richTextBox

public Class MyClass
{
    static BackgroundWorker _bw;
    public MyClass()
    {
        _bw = new BackgroundWorker
        {
            WorkerReportsProgress = true,
            WorkerSupportsCancellation = true
        };
        _bw.DoWork += bw_DoWork;
        _bw.ProgressChanged += bw_ProgressChanged;
        _bw.RunWorkerCompleted += bw_RunWorkerCompleted;
    }
    static void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        foreach (....)
        {
            if (....)
            {
                richtextBox.Text.AppendText("MyText");
            }
        }
        e.Result = true;
    }
    static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e){}
    static void bw_ProgressChanged(object sender, ProgressChangedEventArgs e){}
}
7

7 Answers

39
votes

Using BackgroundWorker doesn't exempt you of the normal threading rules - such as that only the UI thread can access UI components.

If you want to update the UI from a BackgroundWorker other than using the progress/completion events (which are raised on the UI thread) you need to use Control.Invoke / Control.BeginInvoke just as you would in other situations. For example:

if (....)
{
    Action action = () => richtextBox.Text.Add("MyText");
    richtextBox.Invoke(action); // Or use BeginInvoke
}
12
votes

try this code,

BeginInvoke((MethodInvoker)delegate
{
    richtextBox.Text.Add("MyText");
});
3
votes

Using BackgroundWorker component, only the ProgressChanged and RunWorkerCompleted events allow you to invoke methods/properties on UI controls (which should be always done on UI thread). As you are updating the UI in DoWork event which runs on a non-UI thread you are getting this error, you should probably update you UI controls using Invoke or BeginInvoke methods in DoWork event if you want to.

3
votes

To make it cleaner and based on Jon Skeet's suggestion, I made an extension method which does the same, you can change the "this Label control" to this TextBox control or simply use "this Control control" (and basically allow every control to be updated easily):

internal static class ExtensionMethods
{
    /// <summary>
    /// Updates the label text while being used in a multithread app.
    /// </summary>
    /// <param name="control">The control.</param>
    /// <param name="text">The text.</param>
    internal static void UpdateThreadedText(this Label control, string text)
    {
        Action action = () => control.Text = text;
        control.Invoke(action);
    }

    /// <summary>
    /// Refreshes the threaded.
    /// </summary>
    /// <param name="control">The control.</param>
    internal static void RefreshThreaded(this Label control)
    {
        Action action = control.Refresh;
        control.Invoke(action);
    }
}

And then the usage is quite simple:

this.yourLabelName.UpdateThreadedText("This is the text");
this.yourTextBoxName.UpdateThreadedText("This is the text");
this.yourControlName.UpdateThreadedText("This is the text");

or

this.yourLabelName.RefreshThreaded();

Works for me nicely :)

3
votes

You can also try this.

this.Invoke(new MethodInvoker(delegate()
{
    richtextBox.Text.Add("MyText");
}));
0
votes

i think the error stops on this line:

richtextBox.Text.Add("MyText");

your question i similar to this:

BackgroundWorker OnWorkCompleted throws cross-thread exception

0
votes

Add:

e.Result = "MyText";

In your bw_DoWork And:

richTextBox1.AppendText((string)e.Result);

In your bw_RunWorkerCompleted

(Alter it to fit your code)

EDIT:

If it's done many times during the BackgroundWorker's work, you can add:

_bw.ReportProgress(0, "MyText");

to the bw_DoWork and:

richTextBox1.AppendText((string)e.UserState);

to the bw_ProgressChanged.