views:

87

answers:

5

hello,

my view model creates a BackgroundWorker in its constructor. BackgroundWorker updates the model's properties from its DoWork event handler. The code below is a contrived example (ViewModelBase is taken almost verbatim from the MVVM paper).


public class MyViewModel : ViewModelBase
{
    public int MyProperty
    {
        get
        {
            return this.my_property;
        }
        private set
        {
            if (this.my_property != value)
            {
                this.my_property = value;
                this.OnPropertyChanged("MyProperty");
            }
        }
    }

    public MyViewModel()
    {
        this.worker = new BackgroundWorker();
        this.worker.DoWork += (s, e) => { this.MyProperty = 1; };
        this.worker.RunWorkerAsync();
    }
}

my view is bound to this view model:


public partial class MyPage : Page
{
    public MyPage()
    {
        InitializeComponent();
        this.DataContext = new MyViewModel();
        }
}

the problem is that my application crashes intermittently when my page is displayed. this definitely has something to do with the worker thread and data binding in XAML. it seems that if i start the worker from inside the Loaded event handler for the page, the problem goes away but since it is hard to reproduce consistently, i am not sure whether this is the right fix.

does any one have any suggestions or ideas what might be the exact cause?

EDIT: i only can reproduce it if i run it without a debugger. and the error in the system log is InvalidOperationException thrown from ShowDialog, which is not much help. under the debugger it runs fine.

thanks for any help konstantin

+1  A: 

It might have to do with updating the UI from a background thread, but WPF is supposed to handle this for you: http://stackoverflow.com/questions/590590/making-sure-onpropertychanged-is-called-on-ui-thread-in-mvvm-wpf-app

Read this too: http://www.wintellect.com/CS/blogs/jlikness/archive/2009/12/16/dispatching-in-silverlight.aspx

Ray Henry
+2  A: 

Since MyProperty is not thread-safe, strange things can happen when two threads try to access it simultaneously. You should either

  1. make it thread safe (for example, by using the lock keyword) or

  2. make sure that its value is only changed in the UI thread.

I would suggest the second approach, since locking is always a tricky thing. In particular, BackgroundWorker already supports passing values back to the UI thread after the work is done:

public void MyViewModel()
{
    this.worker = new BackgroundWorker();
    this.worker.DoWork += (s, e) => { /* do something */ e.Result = 1; };
    this.worker.RunWorkerCompleted += (s, e) =>
    {
        // all of this happens back in the UI thread
        if (e.Error != null)
        {
            // do some error handling... some exception occurred during DoWork
        }
        else
        {
            this.MyProperty = (int)e.Result;
        }
    };
    this.worker.RunWorkerAsync();
}
Heinzi
+2  A: 

Sounds like your background worker is trying to update the XAML markup / bindings from the background thread. You must invokde the Dispatcher to pass value back to the main thread like this:

Inside your do work event:

Dispatcher.BeginInvoke(delegate()
{
//UI element (test.Text) being updated with value i.ToString() from background thread
test.Text += i.ToString();
});
}
Jeff Gwaltney
Jeff, thanks for your response. it does not explain though why it works most of the time. in addition, my background thread is a part of my view model, which is not concerned with the UI.
akonsu
I read your last response at the bottom of the thread. If you try to update a UI element from outside the main thread you have to use the dispatcher - ProgressChanged is on the main thread because it is intended for the worker to report back to the main thread that a progress change has occured. It's already on the main thread which is why it works for you. Glad you have it working. Blessings, Jeff
Jeff Gwaltney
+1  A: 

Firing INotifyPropertyChanged.PropertyChanged or INotifyCollectionChanged.CollectionChanged (usually from ObservableCollection) events on a data bound object from a background thread is not allowed in WPF.

You can get around this by either using Dispatcher.Invoke to set your property or setting the property in a RunWorkerCompleted handler after returning a value from any long running operations (network or calculations) done in the DoWork handler.

John Bowen
John, thanks. I have an observable collection that the worker populates (so it cannot be done on RunWorkerCompleted because i need to show to the user the partially populated collection). so, in my collection i do use Dispatcher.Invoke to fire the CollectionChanged event. if i were not doing that my app would crash every time i run it, not just sometimes.
akonsu
A: 

i think the problem was that i was updating my properties from inside DoWork event handler. after I have moved the updates to the ProgressChanged handler the problem seems to have disappeared. I even can now update my ObservableCollection from inside ProgressChanged. so it looks like ProgressChanged event handler is invoked on the UI thread by the framework automatically.

akonsu
You are correct. It's basically there to do UI updates (like progress bars) during the background operation.
John Bowen