更新线程中列表框中的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

我不会为此推出我自己的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线程来执行此操作。希望这会有所帮助。