好的,这有道理。但另一个问题是,哪个版本的CollectionView支持多个线程,我如何让我的对象使用它?此类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。
好的,这有道理。但另一个问题是,哪个版本的CollectionView支持多个线程,我如何让我的对象使用它?此类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。
使用:
System.Windows.Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
(Action)delegate()
{
// Your Action Code
});
Invoke
会导致界面冻结,请改用 BeginInvoke
。 - Xaqronpublic class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
if (CollectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
{
DispatcherObject dispObj = nh.Target as DispatcherObject;
if (dispObj != null)
{
Dispatcher dispatcher = dispObj.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
nh.Invoke(this, e);
}
}
}
因为我们使用了BeginInvoke,所以在处理程序调用之前被通知的更改可能会被撤消。当事件参数与列表的新(更改后)状态进行检查时,这通常会导致“索引超出范围”异常的抛出。为了避免这种情况,所有延迟事件都将被替换为重置事件。这可能会在某些情况下导致过多的重绘。
这篇文章由Bea Stollnitz撰写,解释了“NotSupportedException – This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.” 这个错误信息的含义以及为什么会出现这种措辞。
编辑:来自Bea的博客
很遗憾,这段代码会导致异常:“NotSupportedException - 这种类型的CollectionView不支持在与Dispatch线程不同的线程中对其SourceCollection进行更改。” 我理解这个错误消息会让人们认为,如果他们正在使用的CollectionView不支持跨线程更改,那么他们就必须找到支持跨线程更改的CollectionView。好吧,这个错误消息有点误导人:我们提供的所有开箱即用的CollectionView都不支持跨线程集合更改。而且很不幸,在这一点上我们无法修复错误消息,我们被完全限制了。
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
var eh = CollectionChanged;
if (eh != null)
{
Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
let dpo = nh.Target as DispatcherObject
where dpo != null
select dpo.Dispatcher).FirstOrDefault();
if (dispatcher != null && dispatcher.CheckAccess() == false)
{
dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
}
else
{
foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
nh.Invoke(this, e);
}
}
}
}
http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx
dispatcher.Invoke
,即在GUI线程上执行某些操作。这意味着两件事:1.每次向集合添加内容时,工作线程必须停止并等待GUI线程。任务切换是昂贵的,会降低性能。2.GUI线程可能会因为工作量过大而无响应。类似问题的基于计时器的解决方案可以在此处找到https://dev59.com/lW855IYBdhLWcg3wIAga#4530900。 - adrianm您还可以查看:BindingOperations.EnableCollectionSynchronization
。
您可以通过启用集合同步来让WPF管理对集合的跨线程更改,方法如下:
Original Answer翻译成"最初的回答"
BindingOperations.EnableCollectionSynchronization(collection, syncLock);
listBox.ItemsSource = collection;
这告诉WPF,集合可能在UI线程之外被修改,因此它知道必须将任何UI更改调度回适当的线程。
如果您没有锁定对象,还可以重载提供同步回调。
XAML
<Grid>
<DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Name="dgDownloads" VerticalAlignment="Top" Width="548" />
<Label Content="" Height="28" HorizontalAlignment="Left" Margin="0,221,0,0" Name="lblFileCouner" VerticalAlignment="Top" Width="173" />
</Grid>
C#
public partial class DownloadStats : Window
{
private MainWindow _parent;
DispatcherTimer timer = new DispatcherTimer();
ObservableCollection<FileView> fileViewList = new ObservableCollection<FileView>();
public DownloadStats(MainWindow parent)
{
InitializeComponent();
_parent = parent;
Owner = parent;
timer.Interval = new TimeSpan(0, 0, 1);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
dgDownloads.ItemsSource = null;
fileViewList.Clear();
if (_parent.contentManagerWorkArea.Count > 0)
{
foreach (var item in _parent.contentManagerWorkArea)
{
FileView nf = item.Value.FileView;
fileViewList.Add(nf);
}
}
if (fileViewList.Count > 0)
{
lblFileCouner.Content = fileViewList.Count;
dgDownloads.ItemsSource = fileViewList;
}
}
}
Try This:
this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
() =>
{
//Code
}));
VB 版本中有小错误。只需替换 :
Dim obj As DispatcherObject = invocation.Target
通过
Dim obj As DispatcherObject = TryCast(invocation.Target, DispatcherObject)