Update UI thread from portable class library

2020-02-09 05:32发布

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

3条回答
对你真心纯属浪费
2楼-- · 2020-02-09 05:48

Another option that could be easier is to store a reference to SynchronizationContext.Current in your class's constructor. Then, later on, you can use _context.Post(() => ...) to invoke on the context -- which is the UI thread in WPF/WinRT/SL.

class MyViewModel
{
   private readonly SynchronizationContext _context;
   public MyViewModel()
   {
      _context = SynchronizationContext.Current.
   }

   private void MyCallbackOnAnotherThread()
   {
      _context.Post(() => UpdateTheUi());
   }
}
查看更多
放荡不羁爱自由
3楼-- · 2020-02-09 05:52

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.

查看更多
小情绪 Triste *
4楼-- · 2020-02-09 05:55

If you don't have access to the Dispatcher, you can just pass a delegate of the BeginInvoke method to your class:

public class YourViewModel
{
    public YourViewModel(Action<Action> beginInvoke)
    {
        this.BeginInvoke = beginInvoke;
    }

    protected Action<Action> BeginInvoke { get; private set; }

    private void SomeMethod()
    {
        this.BeginInvoke(() => DoSomething());
    }
}

Then to instanciate it (from a class that has access to the dispatcher):

var dispatcherDelegate = action => Dispatcher.BeginInvoke(action);

var viewModel = new YourViewModel(dispatcherDelegate);

Or you can also create a wrapper around your dispatcher.

First, define a IDispatcher interface in your portable class library:

public interface IDispatcher
{
    void BeginInvoke(Action action);
}

Then, in the project who has access to the dispatcher, implement the interface:

public class DispatcherWrapper : IDispatcher
{
    public DispatcherWrapper(Dispatcher dispatcher)
    {
        this.Dispatcher = dispatcher;
    }

    protected Dispatcher Dispatcher { get; private set; }

    public void BeginInvoke(Action action)
    {
        this.Dispatcher.BeginInvoke(action);
    }
}

Then you can just pass this object as a IDispatcher instance to your portable class library.

查看更多
登录 后发表回答