Update UI thread from portable class library

后端 未结 3 1618
庸人自扰
庸人自扰 2020-12-15 12:03

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

相关标签:
3条回答
  • 2020-12-15 12:41

    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());
       }
    }
    
    0 讨论(0)
  • 2020-12-15 12:43

    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.

    0 讨论(0)
  • 2020-12-15 12:54

    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.

    0 讨论(0)
提交回复
热议问题