【问题标题】:Update UI thread from portable class library从可移植类库更新 UI 线程
【发布时间】:2013-01-20 17:48:02
【问题描述】:

我有一个在 Windows Phone 8 上运行的 MVVM Cross 应用程序,我最近移植到使用可移植类库。

视图模型位于可移植类库中,其中一个公开了一个属性,该属性通过数据绑定启用和禁用 Silverlight for WP 工具包中的 PerformanceProgressBar。

当用户按下按钮时,RelayCommand 会启动一个后台进程,该进程将属性设置为 true,这应该启用进度条并进行后台处理。

在将其移植到 PCL 之前,我能够从 UI 线程调用更改以确保启用进度条,但 Dispatcher 对象在 PCL 中不可用。我该如何解决这个问题?

谢谢

【问题讨论】:

  • 我不完全确定在win-phone 中,但你可以使用Application.Current.Dispatcher 来调用更新吗?还是Deployment.Current.Dispatcher

标签: windows-phone-8 mvvmcross portable-class-library


【解决方案1】:

所有 MvvmCross 平台都要求将 UI 操作编组回 UI 线程/单元 - 但每个平台的执行方式不同......

为了解决这个问题,MvvmCross 提供了一种跨平台的方法 - 使用 IMvxViewDispatcherProvider 注入对象。

例如,在 WindowsPhone 上,IMvxViewDispatcherProvider 最终由 MvxMainThreadDispatcherhttps://github.com/slodge/MvvmCross/blob/vnext/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/MvxMainThreadDispatcher.cs 中提供

这实现了InvokeOnMainThread 使用:

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

        return true;
    }

对于 ViewModels 中的代码:

  • 您的ViewModel 继承自MvxViewModel
  • MvxViewModel 继承自 MvxApplicationObject
  • MvxApplicationObject 继承自 MvxNotifyPropertyChanged
  • MvxNotifyPropertyChanged 对象继承自 MvxMainThreadDispatchingObject

MvxMainThreadDispatchingObjecthttps://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);
    }
}

所以...您的 ViewModel 可以调用 InvokeOnMainThread(() =&gt; DoStuff());


还有一点需要注意的是,MvvmCross 自动为属性更新进行 UI 线程转换,这些属性更新通过 RaisePropertyChanged() 方法在 MvxViewModel(或实际上在任何 MvxNotifyPropertyChanged 对象中)发出信号 - 请参阅:

    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));
                });
    }

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


RaisePropertyChanged() 调用的这种自动编组适用于大多数情况,但如果您从后台线程引发大量更改的属性,则可能会有点低效 - 它可能导致大量线程上下文切换。这不是您在大多数代码中需要注意的事情 - 但如果您确实发现这是一个问题,那么它可以帮助更改代码,例如:

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

到:

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

如果您曾经使用过ObservableCollection,请注意 MvvmCross 为这些类触发的 INotifyPropertyChangedINotifyCollectionChanged 事件执行任何线程编组 - 所以这取决于您开发人员来整理这些更改。

原因:ObservableCollection 存在于 MS 和 Mono 代码库中 - 因此 MvvmCross 无法更改这些现有实现。

【讨论】:

    【解决方案2】:

    如果您无权访问 Dispatcher,则只需将 BeginInvoke 方法的委托传递给您的类:

    public class YourViewModel
    {
        public YourViewModel(Action<Action> beginInvoke)
        {
            this.BeginInvoke = beginInvoke;
        }
    
        protected Action<Action> BeginInvoke { get; private set; }
    
        private void SomeMethod()
        {
            this.BeginInvoke(() => DoSomething());
        }
    }
    

    然后实例化它(从有权访问调度程序的类):

    var dispatcherDelegate = action => Dispatcher.BeginInvoke(action);
    
    var viewModel = new YourViewModel(dispatcherDelegate);
    

    或者您也可以在调度程序周围创建一个包装器。

    首先,在您的可移植类库中定义一个 IDispatcher 接口:

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

    然后,在可以访问调度器的项目中,实现接口:

    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);
        }
    }
    

    然后您可以将该对象作为 IDispatcher 实例传递给您的可移植类库。

    【讨论】:

    • 这是一种可行的方法——它基本上正是 MvvmCross 所做的——只是它在平台设置级别使用 IoC 来做到这一点
    • 您的电话...但是,如果您使用的是 MvvmCross,那么您可能会发现使用 InvokeOnMainThread(action) 更容易开发 - 因为这将为您提供每个平台上的预构建实现(并且它包括每次调用之前的CheckAccess() 调用)
    【解决方案3】:

    另一个更简单的选择是在类的构造函数中存储对 SynchronizationContext.Current 的引用。然后,稍后,您可以使用 _context.Post(() => ...) 来调用上下文 - 这是 WPF/WinRT/SL 中的 UI 线程。

    class MyViewModel
    {
       private readonly SynchronizationContext _context;
       public MyViewModel()
       {
          _context = SynchronizationContext.Current.
       }
    
       private void MyCallbackOnAnotherThread()
       {
          _context.Post(() => UpdateTheUi());
       }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-09
      相关资源
      最近更新 更多