我有两个WPF应用程序“UI”,“Debugger”和一个ClassLibrary“BL”。 UI对Debugger和BL的引用。调试器引用BL。 我在BL中收集了名为MyCollection的集合。 UI应用程序启动调试器应用程序,调试器绑定到BL中的集合MyCollection。当我尝试从UI应用程序更改MyCollection集合时,我遇到异常。
A first chance exception of type 'System.NotSupportedException' occurred in PresentationFramework.dll
Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
我在谷歌上搜索,发现了这个:BindingOperations.EnableCollectionSynchronization 我无法弄清楚如何使用它。我不想引用我的BL项目中的任何UI dll。有人能协助我吗?
感谢您的帮助!
答案 0 :(得分:21)
我在Stack Overflow上看到的所有示例都是错误的。从另一个线程修改集合时,必须锁定集合。
在调度程序(UI)线程上:
_itemsLock = new object();
Items = new ObservableCollection<Item>();
BindingOperations.EnableCollectionSynchronization(Items, _itemsLock);
然后从另一个线程:
lock (_itemsLock)
{
// Once locked, you can manipulate the collection safely from another thread
Items.Add(new Item());
Items.RemoveAt(0);
}
本文中的更多信息:http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
答案 1 :(得分:5)
我不确定这是否会有所帮助,但你仍然可以尝试一下。
在Property
中添加Debugger
,其中Collection
与BL
类似
private ObservableCollection<string> _data = new ObservableCollection<string>();
private object _lock = new object();
public ObservableCollection<string> Data { get {return _data;} }
在构造函数中添加以下行
BindingOperations.EnableCollectionSynchronization(_data, _lock);
这将在线上方处理线程安全。
以下是示例
ViewModel(Debugger
)
internal class ViewModelClass : INotifyPropertyChanged
{
private object _lock = new object ();
private ObservableCollection<string> _data;
public ObservableCollection<string> Data
{
get { return _data; }
private set
{
_data = value;
RaisePropertyChanged ("Data");
}
}
private string _enteredText;
public string EnteredText
{
get { return _enteredText; }
set
{
_enteredText = value;
_data.Add (value); RaisePropertyChanged ("EnteredText");
}
}
private void RaisePropertyChanged (string name)
{
var pc = PropertyChanged;
if (pc != null)
pc (this, new PropertyChangedEventArgs (name));
}
public ViewModelClass ()
{
var _model = new ModelClass ();
Data = _model.Data;
_data.CollectionChanged += (s, e) => RaisePropertyChanged ("Data");
}
public event PropertyChangedEventHandler PropertyChanged;
}
模型(BL
)
internal class ModelClass
{
private ObservableCollection<string> _data;
public ObservableCollection<string> Data
{
get { return _data; }
private set { _data = value; }
}
public ModelClass ()
{
_data = new ObservableCollection<string> { "Test1", "Test2", "Test3" };
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow ()
{
InitializeComponent ();
this.DataContext = new ViewModelClass ();
}
}
MainWindow.xaml
<Window x:Class="CollectionSynchronizationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<StackPanel>
<ComboBox IsEditable="True"
ItemsSource="{Binding Data}"
Text="{Binding EnteredText, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
<Button Content="Test" />
</StackPanel>
当窗口加载时,只需在ComboBox
中输入“SomeValue”,然后在按下Tab
键后,您应该会在ComboBox
下拉列表中找到新值
答案 2 :(得分:1)
在这个blog中,您可以找到一个如何使用BindingOperations的简单教程......这很容易。
答案 3 :(得分:0)
WPF应用程序可以使用ItemsControl或其子类之一(ListBox,DataGrid,TreeView,ListView等)显示数据集合。 WPF通过CollectionView的子类将其对集合的所有访问权限引导到。 ItemsControl和CollectionView都与创建ItemsControl的线程有关联,这意味着禁止在其他线程上使用它们并引发异常。实际上,此限制也适用于集合。您可能要在多个线程上使用集合。例如,您想在“数据收集”线程上更新集合(添加或删除项目),同时在“用户界面”线程上显示结果,以便UI在发生数据收集时保持响应状态。在这种情况下,您有责任确保对集合的同步(“线程安全”)访问。通常使用简单的锁定机制或更复杂的同步机制(例如信号量,重置事件等)完成此操作。必须同步应用程序对集合的访问时,还必须确保来自WPF(特别是来自CollectionView)的访问参与其中以相同的同步机制。您可以通过调用EnableCollectionSynchronization方法来做到这一点。
DOC注释非常好,我认为您应该看一下: https://docs.microsoft.com/en-us/dotnet/api/system.windows.data.bindingoperations.enablecollectionsynchronization?view=netcore-3.1
答案 4 :(得分:-3)
当我遇到同样的问题时,我也无法弄清楚如何使用它。
我以自己的集合类型结束,我在那里存储调度程序并在必要时使用它。 请注意,我的命名非常差,这个集合不是线程安全的,远非它。
public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
private readonly Dispatcher _dispatcher;
public ThreadableObservableCollection()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
public void ThreadsafeRemove(T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Remove(item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Remove(item);
callback();
});
}
}
public void ThreadsafeInsert(int pos, T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Insert(pos, item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Insert(pos, item);
callback();
});
}
}
}