2

I'm new to C# and GUI development in general. Please if this is an old question, just direct me to that source and I will take down this question.

My Situation: So I'm writing a GUI that redirects Console output of one function to a text box in my Windows Application Form. The Console output just displays information to the user like serial number of device, current software version, etc.

My Problem: This form works great, except that the process of updating the software takes a couple of minutes which freezes up my form until it is complete. Now, I know that implementing a background worker will alleviate this problem. However, when I implement the background worker I receive the following error.

Cross-thread operation not valid: Control 'TextBox' accessed from a thread other than the thread it was created on.

A summary of my code is as follows:

public class Form1 : Form
{
    this.backgroundWorker1.DoWork += new
    System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
         BackgroundWorker bw = sender as BackgroundWorker;     
         BigProcess(bw);
    }

    private void BigProcess(BackgroundWorker bw)
    {
        // lengthy operation that includes lots of
        Console.WriteLine("feedback stuff for the user");
    }

    private void button1_Click(object sender, EventArgs e)
    {
        this.backgroundWorker1.RunWorkerAsync();
    }
}

In this project I also have a class TextBoxStreamWriter derived from StringWriter which is taking care of the redirecting of console output for me. In TextBoxStreamWriter I am overriding the WriteLine methods and the Write method.

Here is an example of what I am doing:

public override void WriteLine(string value)
{
      base.WriteLine(DateTime.Now.ToString(value));
      textBoxOutput.AppendText(value.ToString() + Environment.NewLine);
      writer.Write(value);
}

An InvalidOperationException is thrown when this method is called.

How can I make this thread-safe? Any help would be appreciated.

Derek W
  • 9,708
  • 5
  • 58
  • 67

3 Answers3

1

You just need to get the update to the textbox back onto the UI thread - e.g:

if (textBoxOutput.InvokeRequired)
{
  Invoke((MethodInvoker)(() => textBoxOutput.AppendText(value.ToString() + Environment.NewLine);
}
else
{
   textBoxOutput.AppendText(value.ToString() + Environment.NewLine)
}
NDJ
  • 5,189
  • 1
  • 18
  • 27
  • Thank you! This worked splendidly! – Derek W Mar 01 '13 at 14:35
  • Glad to help. (Nice detailed question BTW, deserving of many upvotes.) – NDJ Mar 01 '13 at 14:41
  • 1
    You shouldn't be manually marshaling to the UI thread from inside of a `BackgroundWorker`. The entire *point* of using a BGW is that it handles the UI marshaling on your behalf so that you don't have to. If you're not going to leverage any of that functionality then you may as well just use a `Thread`. – Servy Mar 01 '13 at 15:06
1

The BackgroundWorker exposes an event: RunWorkerCompleted which is used to update the UI with the results of the long running operation. That event will be fired in the UI thread.

So all you need to do is handle that event, rather than manually marshalling to the UI thread.

backgroundWorker1.RunWorkerCompleted += (s, args)=> 
    textBoxOutput.AppendText(args.Result.ToString() + Environment.NewLine);

Then you can just set the result in DoWork to whatever you want to be printed.

If you want to update the UI throughout the background processing then you effectively have "progress updates". The BGW has built in support for updating the UI with progress throughout the background work. See the MSDN page for BGW for examples.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • This answer really helped further my understanding of how to effectively utilize the background worker. I'm running this GUI on top of an existing command line interface of the program. The goal of the project was to keep the amount of code to a minimum and not alter the existing structure. I feel like manually marshaling to the UI thread is unavoidable in this case. All that was trying to be accomplished was to take the progress updates from the CLI and map them over to the textbox (hence the TextboxStreamWriter class) it just needed to be pushed to the UI thread after BGW was complete. – Derek W Mar 01 '13 at 21:49
0

Try this:

public override void WriteLine(string value)
{
    if (InvokeRequired)
    {
        Invoke(() => WriteLine(value));
        return;
    }

    base.WriteLine(DateTime.Now.ToString(value));
    textBoxOutput.AppendText(value.ToString() + Environment.NewLine);
    writer.Write(value);
}

This will check if WriteLine is called from a different thread than the one owning the form. If it is, it will ask the thread owning the form to invoke the method again, only on the right thread.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • You shouldn't be manually marshaling to the UI thread from inside of a `BackgroundWorker`. The entire *point* of using a BGW is that it handles the UI marshaling on your behalf so that you don't have to. If you're not going to leverage any of that functionality then you may as well just use a `Thread`. – Servy Mar 01 '13 at 15:07