更新线程中列表框中的ObservableCollection
本文关键字:ObservableCollection 列表 线程 更新 | 更新日期: 2023-09-27 17:51:07
Hy,
我有一个Observable Collection,它与一个列表框绑定。我将日志添加到Observable Collection中。我总是立即将消息添加到可观察收集器中。但是列表只有在循环完成时才会更新,但我想在for循环中添加一个项目时更新它。这就是为什么我使用线程,但我有一些问题。
我有一个线程安全的ObservableCollection:
class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = this.CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
if (dispatcherObject != null)
{
Dispatcher dispatcher = dispatcherObject.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => handler.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
handler.Invoke(this, e);
}
}
}
这是我的测试课:
public partial class MainWindow : Window
{
ThreadSafeObservableCollection<Animal> list = new ThreadSafeObservableCollection<Animal>();
public MainWindow()
{
InitializeComponent();
list.Add(new Animal() { Name = "test1" });
list.Add(new Animal() { Name = "test2" });
this.DataContext = list;
}
private void dsofsdkfd(object sender, RoutedEventArgs e)
{
//Version 1
Task.Factory.StartNew(() => test());
//Version2
/*
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var token = Task.Factory.CancellationToken;
Task.Factory.StartNew(() => test(), token, TaskCreationOptions.None, uiScheduler);
*/
}
public void test()
{
for (int i = 0; i < 10000; i++)
{
list.Add(new Animal() { Name = "test" + i });
System.Threading.Thread.Sleep(1);
}
}
}
请参阅Version1注释的private void dsofsdkdfd(object sender,RoutedEventArgs e(函数。一开始它是有效的,所以每当我添加项目时,列表都会更新。几个条目后,我得到了一个例外:
"开发人员信息(使用文本可视化工具阅读this(:''r''n引发此异常是因为控件的生成器名为"Logger"的"System.Windows.Controls.ListBox Items.Count:1089"已接收到不一致的CollectionChanged事件序列具有Items集合的当前状态。以下内容检测到差异:''r''n累计计数994不同从实际计数1089。[累计计数为(上次复位计数+
添加-#自上次重置后删除(。]''''r''n''r''n下列一个或多个源可能引发了错误的事件:''r''n
System.Windows.Controls.ItemContainerGenerator''r''n
System.Windows.Controls.ItemCollection''r''n
System.Windows.Data.ListCollectionView''r''n*
WpfApplication1.ThreadSafeObservableCollection`1[[WpfApplication1.动物,WpfApplication1,版本=1.0.0.0,文化性=中性,PublicKeyToken=null]]''r''n(星形源被认为更多可能是问题的原因。(''''r''n''r''n最常见的原因是否(a(在不引发相应的事件,以及(b(引发索引不正确的事件或项目参数。''''r''n''r''n异常的堆栈跟踪描述了不一致是检测到的,而不是它们是如何发生的。获取更及时的异常,设置附加属性生成器上的"PresentationTraceSources.TraceLevel"值为"High"并重新运行该场景。一种方法是运行命令类似于以下内容:''n
System.Diagnostics.PPresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator,System.Diagnostics.PresentationTraceLevel.High(窗这导致检测逻辑在CollectionChanged事件,因此它将降低应用程序的速度。''''r''n";
请参见Version2注释的private void dsofsdkdfd(object sender,RoutedEventArgs e(函数。我还使用FromCurrentSynchronizationContext在TaskScheduler中进行了尝试。
然后它没有抛出任何异常,但我遇到了与开头相同的问题,因此只有在for each循环完成时,列表框才会刷新。
如何在添加元素时实现列表框的更新?
向致以最良好的问候
我不会为此推出我自己的ObservableCollection。我只是在UI线程上执行.Add调用。
public void test()
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal {Name = "test" + i};
// invoke list.Add on the UI thread
this.Dispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
注意,由于您在Window子类中,this.Dispatcher
将对应于UI线程的调度器。如果将此逻辑移动到模型或视图模型类,则需要在UI线程上显式捕获Dispatcher.Current
的值,并手动将该调度器传递给后台线程。
EDIT:OP询问有关在FrameworkElement类之外使用Dispatcher的更多信息。下面是你该怎么做。UI线程的调度器是通过调用Dispatcher.CurrentDispatcher
在UI线程上获取的。然后,该调度器被直接传递到后台线程过程中。
public class MainWindowViewModel
{
// this should be called on the UI thread
public void Start()
{
// get the dispatcher for the UI thread
var uiDispatcher = Dispatcher.CurrentDispatcher;
// start the background thread and pass it the UI thread dispatcher
Task.Factory.StartNew(() => BackgroundThreadProc(uiDispatcher));
}
// this is called on the background thread
public void BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal { Name = "test" + i };
// invoke list.Add on the UI thread
uiDispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
}
您需要维护当前的调度器线程。您必须仅在当前调度程序线程中更新集合。一种方法是使用dispatcher类的BiginInvoke((方法。
将当前调度程序保存在构造函数中的变量中,然后在需要时使用它。
_currentDispatcher = Application.Current.Dispatcher;
例如:我们有一个场景,如果出现错误,我们会弹出一个错误窗口。如果错误计数为零,我们需要关闭"错误"窗口。现在,如果我们在另一个线程中(而不是在UI线程上(处理事件和消息,那么我们需要保存UI线程分配器对象,并需要使用它来更新集合或任何其他操作。我正在关闭"错误窗口"。(我还没有准备好更新集合的解决方案。(
if (ErrorNotifications.Count == 0)
_currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ErrorNotificationWindow>(CloseErrorNotificationWindow), _errWindow);
这里CloseErrorNotificationWindow是带有参数_errWindow的方法。
private void CloseErrorNotificationWindow(ErrorNotificationWindow _errWindow)
{
if (_errWindow == null)
return;
if (_errWindow.IsActive)
_errWindow.Close();
}
在CloseErrorNotificationWindow((方法中,您可以更新集合,它不应该给出任何异常,因为您将使用主UI线程来执行此操作。希望这会有所帮助。