【问题标题】:WPF/Multithreading: UI Dispatcher in MVVMWPF/多线程:MVVM 中的 UI 调度程序
【发布时间】:2011-01-07 01:04:26
【问题描述】:

所以说在 MVVM 环境中,我在后台线程中,我想在 ui 控件上运行更新。所以通常我会去 myButton.Dispatcher.BeginInvoke(blabla) 但我无权访问 myButton (因为视图模型无权访问视图的控件)。那么这样做的正常模式是什么?

(我想总是有绑定,但我想知道如何通过调度程序来做)

【问题讨论】:

标签: c# wpf multithreading mvvm


【解决方案1】:

我通常使用Application.Current.Dispatcher:因为Application.Current 是静态的,所以您不需要对控件的引用

【讨论】:

  • 如果没有应用程序对象,您将如何对视图模型进行单元测试?
  • @Geert van Horrik 您可以模拟应用程序对象以对您的视图模型进行单元测试。抱歉,necro 评论 - 我在寻找有效的多线程编程 MVVM 解决方案时偶然发现了这篇 SO 文章。
  • @aaronburro 没有什么能阻止您将其置于可以在单元测试中模拟的抽象之后。
  • 我认为你甚至有一篇关于这样做的不错的博客文章
【解决方案2】:

来自Caliburn Micro源代码:

public static class Execute
{
    private static Action<System.Action> executor = action => action();

    /// <summary>
    /// Initializes the framework using the current dispatcher.
    /// </summary>
    public static void InitializeWithDispatcher()
    {
#if SILVERLIGHT
        var dispatcher = Deployment.Current.Dispatcher;
#else
        var dispatcher = Dispatcher.CurrentDispatcher;
#endif
        executor = action =>{
            if(dispatcher.CheckAccess())
                action();
            else dispatcher.BeginInvoke(action);
        };
    }

    /// <summary>
    /// Executes the action on the UI thread.
    /// </summary>
    /// <param name="action">The action to execute.</param>
    public static void OnUIThread(this System.Action action)
    {
        executor(action);
    }
}

在使用它之前,您必须从 UI 线程调用 Execute.InitializeWithDispatcher(),然后您可以像这样使用它 Execute.OnUIThread(()=&gt;SomeMethod())

【讨论】:

  • 如果我使用 Dispatcher.CurrentDispatcher,我仍然会收到错误消息。如果我使用 Application.Current.Dispatcher,它可以正常工作。
【解决方案3】:

我倾向于让我的 ViewModel 继承自 DependencyObject 并确保它们是在 UI 线程上构建的,这使它们能够完美地处理这种情况 - 它们具有对应于 UI 线程调度程序的 Dispatcher 属性。然后,您无需使用 ViewModel 的实现细节污染您的视图。

其他一些优点:

  • 单元可测试性:您可以在没有运行应用程序的情况下对它们进行单元测试(而不是依赖Application.Current.Dispatcher
  • View 和 ViewModel 之间的松散耦合
  • 您可以在 ViewModel 上定义依赖属性,并且无需编写代码来随着这些属性的变化而更新视图。

【讨论】:

  • DependencyObject 很重,你真正需要的是DispatcherObject。它有一个简单的实现:它的构造函数调用 Dispatcher.CurrentDispatcher 并将值存储在一个字段中以供以后使用。如果您无法向 ViewModel 添加基类,则可以轻松实现 DispatcherObject 正在执行的操作。
【解决方案4】:

Catel 的 ViewModelBase 有一个可以使用的 Dispatcher 属性。

【讨论】:

【解决方案5】:

您可以在您的视图模型上引发一个事件(也许使用命名约定来指示它将从非 UI 线程引发 - 例如 NotifyProgressChangedAsync)。然后,您的附加到事件的 View 可以适当地处理调度程序。

或者,您可以将委托传递给同步函数到您的视图模型(从您的视图)。

【讨论】:

    【解决方案6】:

    将 UI 线程的调度程序传递给 ViewModel 的构造函数并将其存储在 VM 中。

    请注意,每个线程可能有自己的调度程序。您将需要 UI 线程!

    【讨论】:

    • 您可能需要担心多个 Dispatcher 的事实(虽然这是一个糟糕的设计想法,但确实发生了)表明传入 Dispatcher 是一个坏主意。
    【解决方案7】:

    大多数情况下,您不需要 ViewModel 中的 Dispatcher(> 99% 的时间)。早期版本的 .NET 没有将 PropertyChanged 事件适当地编组到 UI 线程,这会导致问题。有一种解决方法,它要求您以一种能够感知 Dispatcher 的方式引发该事件,并且可以在需要时自动编组。 .NET 3.5 及更高版本现在会自动执行此操作。

    因为 Dispatcher 是一个 UI 概念,它在 ViewModel 中的存在是一种巨大的代码气味。它表明你做错了什么。更有可能的是,您要么需要注入一些东西,从您正在操作的 UI 资源中抽象出您的 ViewModel(更改鼠标光标就是一个很好的例子),要么您实际上已经将 View 耦合到了 ViewModel。在后一种情况下,您通常可以通过某种附加行为来解决此问题,这些行为将订阅您的 ViewModel 上的事件和属性更改。

    有一个挂断... CollectionChanged 确实具有线程关联(间接通过由 WPF 自动创建的 CollectionViews),解决此问题的最佳方法是在引发该事件时检查事件委托订阅者上的 SynchronizationContexts。它很臭,但它仍然不需要您将 Dispatcher 传递给 VM。

    【讨论】:

      猜你喜欢
      • 2014-03-23
      • 1970-01-01
      • 1970-01-01
      • 2016-09-17
      • 1970-01-01
      • 1970-01-01
      • 2013-02-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多