Update UI thread from portable class library

Dan Sadler picture Dan Sadler · Jan 20, 2013 · Viewed 8.7k times · Source

I have an MVVM Cross application running on Windows Phone 8 which I recently ported across to using Portable Class Libraries.

The view models are within the portable class library and one of them exposes a property which enables and disables a PerformanceProgressBar from the Silverlight for WP toolkit through data binding.

When the user presses a button a RelayCommand kicks off a background process which sets the property to true which should enable the progress bar and does the background processing.

Before I ported it to a PCL I was able to invoke the change from the UI thread to ensure the progress bar got enabled, but the Dispatcher object isn't available in a PCL. How can I work around this?

Thanks

Dan

Answer

Stuart picture Stuart · Jan 20, 2013

All the MvvmCross platforms require that UI-actions get marshalled back on to the UI Thread/Apartment - but each platform does this differently....

To work around this, MvvmCross provides a cross-platform way to do this - using an IMvxViewDispatcherProvider injected object.

For example, on WindowsPhone IMvxViewDispatcherProvider is provided ultimately by MvxMainThreadDispatcher in https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/MvxMainThreadDispatcher.cs

This implements the InvokeOnMainThread using:

    private bool InvokeOrBeginInvoke(Action action)
    {
        if (_uiDispatcher.CheckAccess())
            action();
        else
            _uiDispatcher.BeginInvoke(action);

        return true;
    }

For code in ViewModels:

  • your ViewModel inherits from MvxViewModel
  • the MvxViewModel inherits from an MvxApplicationObject
  • the MvxApplicationObject inherits from an MvxNotifyPropertyChanged
  • the MvxNotifyPropertyChanged object inherits from an MvxMainThreadDispatchingObject

MvxMainThreadDispatchingObject is https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross/ViewModels/MvxMainThreadDispatchingObject.cs

public abstract class MvxMainThreadDispatchingObject
    : IMvxServiceConsumer<IMvxViewDispatcherProvider>
{
    protected IMvxViewDispatcher ViewDispatcher
    {
        get { return this.GetService().Dispatcher; }
    }

    protected void InvokeOnMainThread(Action action)
    {
        if (ViewDispatcher != null)
            ViewDispatcher.RequestMainThreadAction(action);
    }
}

So... your ViewModel can just call InvokeOnMainThread(() => DoStuff());


One further point to note is that MvvmCross automatically does UI thread conversions for property updates which are signalled in a MvxViewModel (or indeed in any MvxNotifyPropertyChanged object) through the RaisePropertyChanged() methods - see:

    protected void RaisePropertyChanged(string whichProperty)
    {
        // check for subscription before going multithreaded
        if (PropertyChanged == null)
            return;

        InvokeOnMainThread(
            () =>
                {
                    var handler = PropertyChanged;

                    if (handler != null)
                        handler(this, new PropertyChangedEventArgs(whichProperty));
                });
    }

in https://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross/ViewModels/MvxNotifyPropertyChanged.cs


This automatic marshalling of RaisePropertyChanged() calls works well for most situations, but can be a bit inefficient if you Raise a lot of changed properties from a background thread - it can lead to a lot of thread context switching. It's not something you need to be aware of in most of your code - but if you ever do find it is a problem, then it can help to change code like:

 MyProperty1 = newValue1;
 MyProperty2 = newValue2;
 // ...
 MyProperty10 = newValue10;

to:

 InvokeOnMainThread(() => {
      MyProperty1 = newValue1;
      MyProperty2 = newValue2;
      // ...
      MyProperty10 = newValue10;
 });

If you ever use ObservableCollection, then please note that MvvmCross does not do any thread marshalling for the INotifyPropertyChanged or INotifyCollectionChanged events fired by these classes - so it's up to you as a developer to marshall these changes.

The reason: ObservableCollection exists in the MS and Mono code bases - so there is no easy way that MvvmCross can change these existing implementations.