【问题标题】:WPF MVVM Multithreading IssueWPF MVVM 多线程问题
【发布时间】:2010-10-07 21:19:40
【问题描述】:

我看到了与我类似的其他问题,但我还没有看到任何我可以应用以使我的代码工作的问题。

所以我是 MVVM 的新手,我正在尝试获取一些在后台线程中执行的东西来更新我的 UI。我注意到的是,第一次调出 UI,并且后台线程第一次执行,如果集合是 IEnumerable,则 UI 没有完全针对支持数据进行更新。如果集合是 ObservableCollection,则会抛出错误。

根据我的阅读,对集合的更改需要在调度程序线程上执行,但 OnPropertyChanged() 调用不需要。所以有人,请告诉我这是怎么发生的:

我正在更改我的 _Printers 可观察集合:

foreach (PrinterViewModel pv in _Printers)
            {
                DispatcherExec(() =>
                {
                var abilities = from x in _ServerData.Types
                                select new PrinterAbility(
                                    new PrintableType() { ID = x.ID, Name = x.Name, NumInProcUnit = x.NumInProcUnit, PrintersMappedTo = x.PrintersMappedTo, SysName = x.SysName },
                                    x.PrintersMappedTo.Contains(pv.Printer.ID)
                                    );


                    pv.Printer.SetAbilities(abilities);
                });

我的 DispatcherExec 看起来像这样:

 private void DispatcherExec(Action action)
    {
        //Dispatcher.Invoke((Action)delegate 
        //{
        //    action.BeginInvoke(null, null); 
        //}, null);
        Dispatcher.CurrentDispatcher.Invoke((Action)delegate
        {
            action.Invoke();
        }, null);
    }

这是失败的 SetAbilities 代码:

 public void SetAbilities(IEnumerable<PrinterAbility> abilities)
    {
        if (log.IsInfoEnabled)
            log.Info("SetAbilities(IEnumerable<PrinterAbility> abilities): called on printer "+Name);

        List<PrinterAbility> l = new List<PrinterAbility>();
        abilities.ForEach(i =>
            {
                i.PrinterAbilityChanged += new PrinterAbilityChangedEventHandler(OnPrinterAbilityChanged);
                l.Add(i);
            }
            );
        lock (_Abilities)
        {
            foreach (PrinterAbility pa in l)
                _Abilities.Add(pa);
        }
        if (log.IsDebugEnabled)
            log.Debug("SetAbilities(IEnumerable<PrinterAbility> abilities): leaving");
    }

在 _Abilities.Add(pa) 可观察集合上添加它说“这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。”我在想,“你在开玩笑吗?”

此外,我认为对可观察集合中对象的更改会自动使其调用 OnCollectionChanged(),对吗?

提前谢谢大家。

【问题讨论】:

    标签: c# wpf multithreading mvvm


    【解决方案1】:

    使用 Dispatcher.CurrentDispatcher 不是您应该从 BG 线程执行的操作。您需要将 Dispatcher 用于已在 UI 线程上创建的 DependencyObject 派生对象。

    此外,您正在从 BG 线程中迭代 *ViewModel 对象 (PrinterViewModel)。这确实违背了 MVVM。您的模型应该执行异步操作,并且您的 ViewModel(s) 应该以视图可以使用的方式处理这些异步操作(通过 Dispatcher 编组到适当的线程)。

    另外,您正在关闭一个循环变量 (pv)。糟糕,糟糕。这(取决于执行顺序)可能意味着当调度程序出现时,您将在同一个 PrinterViewModel 实例上获得多个 pv.Printer.SetAbilities(...) 调用。在循环内创建一个局部变量并在您的匿名方法中使用它来避免这个问题。

    【讨论】:

    • 感谢您的批评。
    • Application.Current.Dispatcher 怎么样?
    • 好吧,我没有将我的数据源放在 xaml 中,而是将它放在 xaml.cs 中,并将调度程序从其中一个控件传递到我的 VM。那行得通。再次感谢。
    【解决方案2】:

    可能是 thisthis 将有助于通过线程更改 Observable Collection。

    【讨论】:

      【解决方案3】:

      您应该使用与您的任何 WPF 控件关联的 Dispatcher,而不是后台线程的 Dispatcher.CurrentDispatcher。

      还有

      Dispatcher.CurrentDispatcher.Invoke((Action)delegate
              {
                  action.Invoke();
              }, null);
      

      是多余的,应该是

      wpfDispatcher.Invoke(action, null);
      

      最后,对于第一个块,您通常应该避免将循环变量传递给 lambda,使用临时赋值技巧来解决这些鬼鬼祟祟的闭包问题。几乎可以肯定,在这种情况下这不是问题。

      【讨论】:

      • 如果我使用附加到其中一个控件的调度程序,那么我不是将 VM 紧密耦合到 V 吗?
      • 这是唯一的方法。您应该使用与 WPF 线程关联的调度程序。您可以在视图中使用它来整合逻辑,或将其作为参数传递给您正在调用的 VM 类/方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多