使用Dispatcher时真正分离代码和表示

时间:2011-02-10 10:07:28

标签: c# .net wpf

在我的WPF中尝试将我的类逻辑与任何与接口相关的数据分开,并仅提供ObservableCollection属性进行绑定。

问题是,当我从其他线程访问那些绑定的OC时,我需要通过调度程序执行此操作。结果是,每当其中一个方法尝试更新OC时,我都需要添加许多隐藏在我的类中的Dispatcher.Invoke()调用。

我怎样才能以更干净,更分散的方式做到这一点,所以调度员调用是从我的方法中抽象出来的呢?

9 个答案:

答案 0 :(得分:3)

我没有银弹。但是如果您确定并且已经准备好承担隐式UI委派的责任,您可以始终从ObservableCollection继承,覆盖方法并将所有请求分派给UI。

但下面的代码让我很害怕:

// somewhere in thread pool:
for(int i = 0; i < 1000; i++)
{
   _dispatcherAwareCollection.Add(i);
}

它似乎是无辜的,但它会阻止调用线程1000次。替代方案可能是您的特定BulkXXX()方法,这将延迟通知,直到处理完所有元素。这个解决方案也不完美,因为你想要一个可以让你无缝交换集合的抽象,但BulkXXX()方法非常特定于新集合。

答案 1 :(得分:2)

选项1

我认为您应该使用MVVM模式更好地分离您的代码,如果您不熟悉它,我强烈建议您查看following video,因为它准确地解释了您要查找的内容

但是,具体而言,在您的情况下,您应该拥有带有常规集合的模型类(例如List),您可以在其中执行线程中的所有工作。您的 ViewModel 应包含ObservableCollections并将松散与模型中存在的集合连接,例如,您可以选择通过ViewModel中的事件订阅到某个更新逻辑在你的模型中。您仍然需要使用Dispatcher来更新OC,但您只需要执行一次。

选项2

您只需使用here所述的解决方案即可。基本上,他从OC创建了一个新的派生类,允许您自动从代码中调度更改,而无需自己更新调度程序。

答案 2 :(得分:2)

常见的方法是在视图模型上(可能在所有视图模型的基类中)具有可以在外部注入的Dispatcher属性。可以将它放在视图模型中,因为视图模型应该知道UI概念,但它不应该知道特定的视图(布局,控件等),当然它不应该有对视图的引用。 / p>

您可以做的是通过创建一个将调度程序抽象出去的帮助程序或服务,可以更轻松地将代码分派给Dispatcher线程。例如,您可以创建一个这样的帮助器:

public class AsyncHelper
{
    public static void EnsureUIThread(Action action) 
    {
        if (Application.Current != null && !Application.Current.Dispatcher.CheckAccess()) 
        {
            Application.Current.Dispatcher.BeginInvoke(action, DispatcherPriority.Background);
        }
        else 
        {
            action();
        }
    }
}

无论何时需要更新可观察集合,都可以将代码包装在该辅助方法中:

AsyncHelper.EnsureUIThread(() =>
{
    // Update you observable collections here
});

或者,您可以更进一步使用AOP(例如PostSharp)以声明方式(使用属性)指定应在UI线程中执行方法。

最后,请注意,您必须仅向UI线程发送集合更新。可以从后台线程安全地更新常用属性。更新将由绑定机制自动分派到UI线程。可能在将来版本的WPF中,也可以支持从后台线程更新集合。

答案 3 :(得分:1)

如果您知道如何编写线程安全,那么您可以自己写一个AsyncObservableCollection。然后,您可以将Dispatcher调用封装在其中。 问题是你不会使用.Net - Framework中提供的标准ObservableCollection。这会增加应用程序中出错的风险。

另一种选择是实现一个WrapperClass,它包含并公开ObservableCollection的绑定,并具有修改集合的方法。


public class WrapperClass<T>
{
   public ObservableCollection<T> Collection {get; set;}

   public void Add(T item) 
   { 
      //do your dispatcher magic here 
   }
   ...
}

要修改集合,请实现其中的方法。这里的问题是,不能保证其他人也会使用这些方法。

答案 4 :(得分:0)

我担心您将不得不等待下一版本的wpf

来自this post

  

我们可以期待在下一版WPF中看到的一些小块包括:

     
      
  • 使用新的SilverlightHost元素托管Silverlight内容,没有空域问题(无法在本机Windows hWnd内容上重叠WPF内容)
  •   
  • 使用托管的基于本地hWnd的内容(如WebBrowser,HwndHost和WindowsFormsHost)整体更好地管理空域
  •   
  • 为在后台线程上创建的集合启用绑定和更改通知
  •   
  • 与UI虚拟化更好地集成
  •   
  • 功能区控件的集成
  •   
  • 以及更多
  •   

答案 5 :(得分:0)

使用SynchronizationContext 代替Dispatcher。 SynchronizationContext是.NET中线程同步的常用功能,同时Dispatcher是专门为WPF开发的。

答案 6 :(得分:0)

你可能想要使用像MTObservableCollection这样的东西。我在一个项目中使用过这个,它的工作非常出色。基本上,它通过分析从中分配处理程序的线程并相应地调度来提升集合更改事件时为您执行的所有调度工作。

即使您不打算采用此选项,这篇文章也值得一读。

答案 7 :(得分:0)

我有一个扩展名:

 public static class DispatcherInvoker
 {       

    public static void AddOnUI<T>(this ICollection<T> collection, T item)
    {
        Action<T> addMethod = collection.Add;
        Application.Current.Dispatcher.BeginInvoke(addMethod, item);
    }
 }

编辑: 我从stackoverflow帖子偷了它,但忘记了哪一个

答案 8 :(得分:0)

如果您需要考虑模型层中的线程,我认为您需要进行大量耦合。

您应该做的是不将模型直接连接到GUI。正如其他人所说,在中间使用一层(MVVM)。

这意味着您让MVVM层响应来自可观察集合的更改通知。 MVVM层决定是否以及如何将这些通知传递给GUI。查看here以获得降低GUI更新频率以使其可用的方法。

简而言之: 如果您愿意,请继续在模型层中使用ObeservableCollection,但不要在GUI绑定中直接使用它。让另一层接收通知并控制GUI更新。