1

I'm trying to get a BackgroundWorker to update the UI during program logic execution. But I get the error:

EDIT! So my actual goal, which I guess isn't clear from the sample I provided, is to be able to perform calculations while updating the UI. See the updated code sample below.

InvalidOperationException: The calling thread cannot access this object because a different thread owns it.

My C# and xaml follow:

public partial class MainWindow : Window
{
    private readonly BackgroundWorker worker = new BackgroundWorker();

    public MainWindow()
    {
        InitializeComponent();

        worker.DoWork += worker_DoWork;
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        test.Background = Brushes.Orange;

        for (double x = 0; x < 10000000000; )
        {
            x++;
        }

        test.Background = Brushes.Red;
    }

    private void test_Click(object sender, RoutedEventArgs e)
    {
        worker.RunWorkerAsync();
    }
}

And then, my XAML:

<Button Name="test" Click="test_Click">This is a button!</Button>
economistdolly
  • 395
  • 4
  • 20
  • Controls may only be accessed by the thread that created them. Take comfort in the fact that this is actually a good thing. – Ed S. Sep 07 '12 at 18:25
  • possible duplicate of [How to deal with cross-thread access exceptions?](http://stackoverflow.com/questions/11923865/how-to-deal-with-cross-thread-access-exceptions) – H.B. Sep 07 '12 at 19:20

7 Answers7

2

You can't directly access user interface objects from background threads. You need to either marshal the call back onto the UI thread (via Dispatcher.Invoke) or do the update within a progress or completion event of the BackgroundWorker, as these are already run on the UI thread.

That being said, in this case, the BW is only updating the UI, so you should just do it directly in your Button's event handler, as there is no other "work" to perform.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
2

Try this

void worker_DoWork(object sender, DoWorkEventArgs e)
{
   Dispatcher.BeginInvoke(new Action(() => { test.Background = Brushes.Orange; }));
}

you need to execute it from the UI dispatcher.

Raúl Otaño
  • 4,640
  • 3
  • 31
  • 65
1

You can't do a UI update from the background thread; use either the RunWorkerCompleted or the ProgressChanged events to update the UI.

worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    test.Background = Brushes.Orange;
}
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
1

Ok the previous lock your app too, try this:

void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        for (double x = 0; x < 10000000000; )
        {
            x++;
        }
    }


    private void test_Click(object sender, RoutedEventArgs e)
    {
        test.Background = Brushes.Orange;

        worker.RunWorkerCompleted += (_,__) =>  test.Background = Brushes.Red; 
        worker.RunWorkerAsync();

    }
Raúl Otaño
  • 4,640
  • 3
  • 31
  • 65
0

You need to do it in the BackgroundWorker's RunWorkerCompleted event. Within this event, you'll need to use the Dispatcher and one of it's 2 main methods: Invoke and BeginInvoke.

Big Daddy
  • 5,160
  • 5
  • 46
  • 76
0

This works for any code:

void CodeToExcecute()
{
        test.Background = Brushes.Orange;

        for (double x = 0; x < 10000000000; )
        {
            x++;
        }

        test.Background = Brushes.Red;

}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
   Dispatcher.BeginInvoke(new Action(() => CodeToExcecute() ));
}
Raúl Otaño
  • 4,640
  • 3
  • 31
  • 65
  • ...and completely defeats the purpose of having another thread as it pushes everything back to the UI. – H.B. Sep 07 '12 at 19:35
  • I think that this is the idea: to separate code logic from visual. Any way, i only had used BackgroundWorker inside MVVM logic, i think that you can modify the properties of ViewModel, and the View get updated (i have to test it). – Raúl Otaño Sep 07 '12 at 20:01
0

i have tried some other code, and do some researching, and yes, there exist ways for to modify view from another thread, using the SynchronizationContext.

Please see this example:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var sync = SynchronizationContext.Current;
            BackgroundWorker w = new BackgroundWorker();
            w.DoWork+=(_, __)=>
                {
                    //sync.Post(p => { button.Content = "Working"; }, null);
                    int j = 0;
                    for (int i = 0; i < 10; i++)
                    {
                        j++;
                        sync.Post(p => { button.Content = j.ToString(); }, null);
                        Thread.Sleep(1000);
                    }
                    sync.Post(p => { button.Background = Brushes.Aqua; button.Content = "Some Content"; }, null);
                };
            w.RunWorkerAsync();
        }

And this is the view:

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button x:Name="button" Content="Some Content" Click="Button_Click"/>
    </Grid>
</Window>

This code, updates several times the view (a button in this case). I think this solve your initial question.

Raúl Otaño
  • 4,640
  • 3
  • 31
  • 65