在不同线程上修改时嵌套的 ObservableCollections 抛出异常

Nested ObservableCollections throwing exception when modified on different thread

我正在使用一个 TreeView,它的 ItemsSource 绑定到我的 ViewModel 中的一个 ObservableCollection。我正在使用一个 HierarchicalDataTemplate,它的 ItemsSource 绑定到另一个 ObservableCollection。这两个 ObservableCollections 都从不同的线程动态更新。

<TreeView x:Name="planeView" BorderThickness="0" MaxHeight="500" ItemsSource="{Binding Planes}"  SelectedItemChanged="treeview_OnSelectedItemChanged">
   <TreeView.Resources>
      <HierarchicalDataTemplate DataType="{x:Type models:Plane}" ItemsSource="{Binding Messages}">
         <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding PlaneId}" />
            <TextBlock Text=" [" Foreground="Blue" />
            <TextBlock Text="{Binding Messages.Count}" Foreground="Blue" />
            <TextBlock Text="]" Foreground="Blue" />
         </StackPanel>
      </HierarchicalDataTemplate>
      <DataTemplate DataType="{x:Type models:Message}">
         <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding TimeStamp}" />
         </StackPanel>
      </DataTemplate>
   </TreeView.Resources>
</TreeView>

最终结果是一棵树,其中顶级节点是动态的,这些节点的内容也是动态的。

当我开始使用单个 ObservableCollecction 开发它时,我 运行 进入异常:

This type of CollectionView does not support changes to its Source Collection from a thread different from the Dispatcher thread

我发现有多个来源建议使用 BindingOperations.EnableCollectionSynchronization(...)

这解决了我的问题,我继续开发。但是,当我嵌套它们并使它们都动态时。那个异常又回来了。我确保在两个可观察集合上都启用了同步。但是,我仍然得到异常,通常异常是在 UI 中可视化显示一两个项目之后。 (没有一致的)所以这似乎是某种竞争条件,但我不知道如何解决它。

下面是我的 ViewModel 和一些支持 类。

public class MyViewModel 
{
  private object _lock = new object();

  public ObservableCollection<Plane> Planes { get; set; }

  public MyViewModel()
  {
     Planes = new ObservableCollection<Plane>();
     BindingOperations.EnableCollectionSynchronization(Planes, _lock);
     MessageSystem.Subscribe<PlaneInformationMessage>(HandlePlaneMessage);
  }

  // this method is executed on a different thread
  public void HandlePlaneMessage(PlaneInformationMessage planeMsg)
  {
     Message msg = new Message();

     // set the timestamp
     string timeStampString;
     if (planeMsg.TimeOfDay.HasValue)
     {
        timeStampString = planeMsg.TimeOfDay.Value.ToString(@"hh\:mm\:ss");
     }
     else
     {
        timeStampString = "--:--:--";
     }

     msg.TimeStamp = timeStampString;
     msg.Content = planeMsg.OriginalMessageContents;

     var plane = new Plane();
     plane.PlaneId = planeMsg.TailNumber.ToString();

     int index = Planes.IndexOf(plane);

     if (index < 0)
     {
        plane.Messages.Add(msg);
        Planes.Insert(0, plane);
     }
     else
     {
        Debug.WriteLine(msg.TimeStamp);
        Planes[index].Messages.Insert(0, msg); // This line throws the exception!!
     }
  }

支持类:

public class Plane : IEquatable<Plane>
{
  private object _lock = new object();

  public string PlaneId { get; set; }

  public ObservableCollection<Message> Messages { get; set; }

  public Plane()
  {
     Messages = new ObservableCollection<Message>();
     BindingOperations.EnableCollectionSynchronization(Messages, _lock);
  }

  public bool Equals(Plane other)
  {
     if (PlaneId == other.PlaneId)
        return true;
     else
        return false;
  }

}

public class Message
{
  public string TimeStamp { get; set; }
  public string Content { get; set; }
  public string Metadata { get; set; }
}

你可以尝试使用Dispatcher.Invoke方法:

https://msdn.microsoft.com/es-es/library/system.windows.threading.dispatcher.invoke(v=vs.110).aspx

试试这样更新:

Application.Current.Dispatcher.Invoke(() => Planes[index].Messages.Insert(0, msg));