1

I'd like to use a BackgroundWorker for background operations because I thought there is no need to take care of "BeginInvoke" etc. when updating WinForm-Controls. Is that right? As far as I know, you can update WinForms controls directly by using the ProgressChanged and RunWorkerCompleted eventhandlers.

But I can't, I although get the following exception:

Control control name accessed from a thread other than the thread it was created on

Some code:

public partial class ConfigurationForm : Form
{

    public ConfigurationForm()
    {
        InitializeComponent();
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.WorkerSupportsCancellation = true;
        label1.Text = String.Empty;
        // [...]
    }

    private void StartButton_Click(object sender, EventArgs e)
    {
        if (backgroundWorker1.IsBusy != true)
        {
            label1.Text = "Converting...";
            backgroundWorker1.RunWorkerAsync();
        }
    }

    private void CancelButton_Click(object sender, EventArgs e)
    {
        if (backgroundWorker1.WorkerSupportsCancellation == true)
        {
            backgroundWorker1.CancelAsync();
        }
        progressBar1.Dispose();
        this.Close();
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
        // EXCEPTION here, why?
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        Converter c = new Converter();
        c.Start(worker, e);
    }

    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // EXCEPTION in all cases, why?
        if (e.Cancelled == true)
        {
            label1.Text = "Canceled";
        }
        else if (e.Error != null)
        {
            label1.Text = "Error: " + e.Error.Message;
        }
        else
        {
            label1.Text = "Done!";
        }
    }
}

I have to say, this is not a WinForms application but an VSTO PowerPoint add-in. The Form above gets created by the add-in like this when the user is clicking an icon in the ribbon bar of PowerPoint:

//Do I need [STAThread] here? but doesn't seem to work anyway
private void button1_Click(object sender, RibbonControlEventArgs e)
{
    ConfigurationForm config = new ConfigurationForm();
    config.Show();
}

Can you tell me what's the problem here?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
alapeno
  • 2,724
  • 6
  • 36
  • 53
  • Check this out http://stackoverflow.com/questions/661561/how-to-update-gui-from-another-thread-in-c – GETah Apr 19 '12 at 17:46
  • Thanks but I cannot see how this could help me. I know that there is the need to invoke controls when using "normal" threads. In order to avoid that I use a backgroundworker which abstracts this whole mechanism for me but it doesn't work the way I have expected. – alapeno Apr 19 '12 at 17:52
  • 1
    UI controls can only be touched from the main UI thread. Background workers are threads different from the UI thread so can't directly access UI controls. This is the case for both WinForms and WPF – GETah Apr 19 '12 at 17:57
  • The BackgroundWorker is simply a class that makes multi-threading easier to handle. You still have to invoke UI calls on the UI thread, as the BackgroundWorker does it's work on a different thread. – Ryan Alford Apr 19 '12 at 18:01
  • Yes I know, but the thing is that ProgressChanged and RunWorkerCompleted handlers indeed should be executed on the UI Thread when they get called, that's in my opinion the nice thing when using Backgroundworkers. But in my case, that does not work. I think this has something to do with the VSTO add-in mentioned above. – alapeno Apr 19 '12 at 18:01
  • That Cancel button can't work — and why are you disposing the progressbar specifically? You can't close the form from the Cancel button's click event because the bgw is still running, you have to do it in the RunWorkerCompleted event. – LarsTech Apr 19 '12 at 18:03
  • Thanks Lars, you are absolutely right. But I thin this is not the problem here :) – alapeno Apr 19 '12 at 18:04
  • @alapeno, there **is** a difference between calling `_backgroundWorker.ReportProgress()` and explicitly invoking the method that is attached to `ProgressChanged` event. When you use `_backgroundWorker.ReportProgress()`, it internally performs an invoke for you whereas calling your handler directly will not. How are you reporting progress from within the method that is performing the task? – Tung Apr 19 '12 at 18:10
  • Hi Tung, from within the class that does the background work I do `worker.ReportProgress()`. The worker object, as you can see above, is passed as a parameter. – alapeno Apr 19 '12 at 18:12
  • 3
    [According to the answer to this question](http://social.msdn.microsoft.com/forums/en-US/vsto/thread/c97bf25a-bc54-4b74-9ad7-59bcfe1431f1/), "Each VSTO add-in is placed in its own appdomain, partly for isolation reasons..." The background worker documentation states that "BackgroundWorker events are not marshaled across AppDomain boundaries". – Tung Apr 19 '12 at 18:37
  • 1
    Your assumptions and expectations are correct for a normal application. The ProgressChanged handler is supposed to run on the thread that created the worker. But @Tung hit the bullseye, I think. You'll need to invoke in this case. – Igby Largeman Apr 19 '12 at 18:46
  • Thanks to both of you, I think is is the reason. What would you recommed? Still stick to the Backgroundworker and implement invoking manually or replace it with common threads? The main advantage of the worker is gone in my opinion. – alapeno Apr 19 '12 at 18:56
  • 2
    http://social.msdn.microsoft.com/Forums/en-US/vsto/thread/283ae1d2-f880-4f05-ba9f-48b29def7f8d/ – Hans Passant Apr 19 '12 at 19:04
  • @HansPassant, good find! – Tung Apr 19 '12 at 19:22

2 Answers2

1

I posted the link but I don't actually think it is the best solution. Clearly the failure occurs because you never called Application.Run() or used Form.ShowDialog(). You can assign the context explicitly as shown but you can get some very tricky problems if you don't do it right. Like assigning it more than once.

The better fix is to ask it to automatically install itself. Which then ensures that whatever form you create will then install it only when it wasn't done before. Put this in front of the form creation code:

        WindowsFormsSynchronizationContext.AutoInstall = true;

With the big advantage that if the code ever gets repeated, you won't create another instance of it and potentially screw up the thread's ExecutionContext.

Do consider ShowDialog() as another fix. If I'm not mistaken then you now also have a problem with tabbing and shortcut keystrokes.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
0

Your assumption would be correct for Windows Forms. The way it works though is BackgroundWorkers uses the SynchronizationContext of the current thread. In a windows app, that will be a WindowsFormsSynchronizationContext, which does the marshalling for you.

In a VSTO app, it won't be. It will probably just be the default one, which simply executes the methods. The link from Hans Passant has the code you need to get it to work as expected. I.e.:

System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());

...create and start your background worker here...
Steven P
  • 1,956
  • 1
  • 16
  • 17