3

我想知道我需要做什么才能使模型在 MVVM 中线程安全。假设我有以下类,它被实例化为单例:

public class RunningTotal: INotifyPropertyChange
{
   private int _total;
   public int Total
   {
      get { return _total; }
      set
      {
         _total = value;
         PropertyChanged("Total");
      }
   }
   ...etc...
}

我的视图模型通过一个属性公开它:

public RunningTotal RunningTotal { get; }

我的视图绑定了一个文本块,即{Binding Path=RunningTotal.Total}.

我的应用程序有一个后台线程,它会定期更新 Total 的值。假设没有其他东西更新 Total,我应该做什么(如果有的话)使所有这些线程安全?

现在,如果我想做类似的事情但使用 type 的属性Dictionary<>,或者ObservableCollection<>?哪些成员(添加、删除、清除、索引器)是线程安全的?我应该改用 ConcurrentDictionary 吗?

4

6 回答 6

8

我的应用程序有一个后台线程,它会定期更新 Total 的值。假设没有其他东西更新 Total,我应该做什么(如果有的话)使所有这些线程安全?

对于标量属性,你不需要做任何特别的事情;该PropertyChanged事件会自动编组到 UI 线程。

现在,如果我想做类似的事情但使用 Dictionary<> 或 ObservableCollection<> 类型的属性怎么办?哪些成员(添加、删除、清除、索引器)是线程安全的?我应该改用 ConcurrentDictionary 吗?

不,这不是线程安全的。如果您ObservableCollection<T>从后台线程更改 an 的内容,它将中断。您需要在 UI 线程上执行此操作。一种简单的方法是使用一个在 UI 线程上引发其事件的集合,就像这里描述的那样。

至于Dictionary<TKey, TValue>,它不会在其内容更改时发出通知,因此 UI 无论如何都不会收到通知。

于 2012-06-13T13:22:17.633 回答
2

假设有两个线程正在更新Total,并且您想_totalPropertyChanged方法中记录所有更改。现在有一个竞争条件PropertyChanged可能会丢失一个值。当线程在调用过程中阻塞时会发生这种情况set_Total。它更新_total但尚未调用 PropertyChanged。与此同时,另一个线程更新_total为另一个值:

thread1: _total = 4;
thread2: _total = 5;
thread2: PropertyChanged("Total");
thread1: PropertyChanged("Total");

现在,PropertyChanged永远不会以 4 的值调用。

您可以通过将值传递给PropertyChanged方法或在 setter 中使用锁来解决此问题。

由于您说您有一个更新此属性的线程,因此不可能出现竞争条件。只有当多个线程(或进程)同时更新同一事物时才会出现这种情况。

于 2012-06-13T13:20:15.747 回答
2

模型应该像任何代码一样以线程安全的方式编写;由您决定是否使用锁、并发容器或其他任何方式来执行此操作。模型只是库代码,它(几乎)不应该知道它的功能将被 MVVM 应用程序使用。

但是,VM 必须在 UI 线程中工作。这意味着他们通常不能依赖模型中的事件来自 UI 线程,因此如果订阅的事件不是来自 UI 线程,他们必须编组调用或将它们存储在任务队列中。

因此,VM 是一个应该以特定方式关心线程安全的地方,而不是模型需要的地方。

反过来,View 代码通常可以快乐地忽略所有线程问题:它在专用 UI​​ 线程中获取所有消息/调用/事件/任何内容,并在 UI 线程中进行自己的调用。


特别针对您的情况,您的代码不是模型而是VM,对吗?在这种情况下,您必须在 UI 线程中触发您的事件,否则 View 会不高兴。

于 2012-06-13T13:18:07.363 回答
1

这个问题提供了 ObservableCollection 的线程编组版本

如何从后台线程正确更新数据绑定 datagridview

但是,您仍然需要担心线程之间的争用,这将要求您在更新资源时锁定资源,或者使用诸如 Interlocked.Increment 之类的东西。

如果一个线程正在更新,而另一个线程正在读取,则存在读取在更新的中途完成的可能性(例如,正在修改 Int64。前半部分(32 位)已在一个线程中更新,并且在后半部分更新之前,从第二个线程读取值。读取的值完全错误)

这可能是也可能不是问题,具体取决于您的应用程序将要执行的操作。如果错误的值会在 GUI 上闪烁 1 秒,那么它可能没什么大不了的,锁的性能损失可以忽略不计。如果您的程序要根据该值采取行动,那么您可能需要将其锁定。

于 2012-06-13T13:28:27.770 回答
0

一些更复杂的事情实际上是如此简单......在您的视图中实例化您的视图模型时,只需将调度程序向下传递到 ctor 中。

    ServerOperationViewmodel ViewModel;        
    public pgeServerOperations()
    {
        InitializeComponent();
        ViewModel = new ServerOperationViewmodel(Dispatcher);
    }

然后在您的视图模型中:

Dispatcher UIDispatcher;
public ServerOperationViewmodel(Dispatcher uiDisp)
{
    UIDispatcher = uiDisp;
}

并像普通的 UI 调度程序一样使用它。

UIDispatcher.Invoke(() =>
{
  .......
});

我承认我对 MVVM 还是很陌生,但我不认为这违反了 MVVM 的座右铭。

于 2015-03-18T22:30:57.283 回答
0

一个简单的答案是,您需要通过 UI Thread 的 Dispatcher 来安排 UI Thread 中的属性更新。这会将更新操作放入不会使应用程序崩溃的队列中。

private void handler(object sender, EventArgs e)
{
    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate { updates(); });
}

private void updates() { /* real updates go here */ }
于 2015-01-22T02:37:21.023 回答