次の方法で共有


Using the Dispatcher with MVVM

When writing an MVVM application, you want to separate from the UI. However you also need to make sure that UI updates happen on the UI thread. Changes made through INotifyPropertyChanged get automatically marshaled to the UI thread, so in most cases you’ll be fine. However, when using INotifyCollectionChanged (such as with an ObservableCollection), these changes are not marshaled to the UI thread.

This means that unless you modify your collection on the UI thread, you’ll get a cross threading error. Thus the problem. We’re in the ViewModel and we don’t have access to the Dispatcher. That’s on a View object. How do we update the collection on the UI thread?

I came up with a small static class to help with this:

 
 public static class DispatchService
{
    public static void Invoke(Action action)
    {
        Dispatcher dispatchObject = Application.Current.Dispatcher;
        if (dispatchObject == null || dispatchObject.CheckAccess())
   {
            action();
        }
        else
        {
            dispatchObject.Invoke(action);
        }
    }
}

When your ViewModel gives the DispatchService actions, it can run them on the correct thread. Which is, in the case of unit tests, always the current thread. A call to the DispatchService may look like this:

 DispatchService.Invoke(() =>
{
    this.MyCollection.Add("new value");
});

The use of a simple lambda statement makes it a relatively lightweight way of updating on the correct thread.

Comments

  • Anonymous
    June 14, 2010
    "In your main window constructor, set the DispatchObject to its Dispatcher." Can I do this in XAML? How? Thanks!

  • Anonymous
    June 25, 2010
    Can I use this for any objects, not just Collections? Is that advisable?

  • Anonymous
    July 26, 2010
    I don't think you can do this in XAML. And yes, you can use the dispatcher for anything you need to do. It's just most useful when dealing with collections because when settings properties it does the work on the correct thread automatically.

  • Anonymous
    March 07, 2011
    Simply solution to an annoying problem, thanks alot!

  • Anonymous
    July 21, 2011
    Why not just habitually derive your viewmodel from DispatcherObject and call VerifyAccess( ) in all your public methods and properties?  Dispatcher is simply a threading model, it has no necessary correlation to GUI, and I'd wager people aren't clogging their viewmodels with locks. Or let's just say, I would be very disappointed if they were.

  • Anonymous
    February 18, 2012
    Straight to the point! I added the code:  DispatchService.DispatchObject = this.Dispatcher; in my App.Xaml.cs class constructor and then invoked DispatchService.Dispatch inside the BackgroundWorker.DoWork method, in order to have the UI updated as a consequence of the methods inside the DoWork code. Thanks!

  • Anonymous
    May 28, 2012
    The comment has been removed

  • Anonymous
    May 29, 2012
    Good point, Vitaliy. I had actually already updated this in my code; I just had not gotten around to updating the blog post. Done.

  • Anonymous
    December 10, 2013
    I don't have App.xaml because my application is using window form. In one window I am loading xaml view. In this case where should I initialize DispatchObject.

  • Anonymous
    September 15, 2014
    Can I totally avoid DispatcherService in my viewmodel if i don't want to update UI on collection changed event ? Instead I can use RaisePropertyChanged(()=>MyCollection) to update the UI once asynchronous operation is over. But it confuses me since i have seen application crashing while updating properties not collection from viewmodel which are data bound to UI. please elaborate the behavior

  • Anonymous
    October 08, 2014
    This also work fine for unit tests if I add check for Application.Current != null first. Final code become like this then. public static void Invoke(Action action)        {            if (Application.Current != null)            {                Dispatcher dispatchObject = Application.Current.Dispatcher;                if (dispatchObject == null || dispatchObject.CheckAccess())                {                    action();                }                else                {                    dispatchObject.Invoke(action);                }            }            else            {                action();            }        }

  • Anonymous
    July 30, 2016
    Thank you so much! This solved my problem!