如何从另一个线程正确地更新ObservableCollection

本文关键字:正确地 更新 ObservableCollection 线程 另一个 | 更新日期: 2023-09-27 17:51:19

如果我使用ActionBlock进行数据库调用,并且需要更新GUI(可能是ObservableCollection)。是循环通过结果集和使用Dispatcher.BeingInvoke一个很好的解决方案,还是有一个更好的方法?

我想一次加载一行到GUI,因为即使启用了虚拟化,似乎如果我一次更新整个可观察集合,GUI将挂起,直到它可以渲染整个数据网格。

一些模拟情况的示例代码:

ActionBlock<Func<Task>> _block = new ActionBlock<Func<Task>>(action => action());
        _block.Post(async () =>
            {
                await Task.Delay(1000); // Perhaps Long database read
                for (int i = 0; i < 1000000; i++) // Perhaps looping over database result set
                {
                    await Dispatcher.BeginInvoke( // Need to update GUI
                        new Action(
                            () =>
                            {
                                // Add new object to collection (GUI will update DataGrid one row at a time).
                                MyModel.MyCollection.Add(new MyClass() { MyInt = i });
                            }
                        ), DispatcherPriority.Background
                    );
                }
            });

如何从另一个线程正确地更新ObservableCollection

如果您添加调用调度。BeginInvoke内部循环,然后你更新你的UI 100k次。理想情况下,我会这样做:

//do as much work as possible in background thread
var items = new MyClass[100000];
for (int i = 0; i < 1000000; i++) {
   items[i] = new MyClass{ MyInt = i;}
}
Dispatcher.BeginInvoke(new Action(() => //update UI just once
   MyModel.MyCollection = new ObservableCollection(items);
));

如果你的虚拟化工作,应该没有问题。

为了避免在UI线程中添加大量数据,您可以将其拆分为更小的数据部分:

for (int i = 0; i < 100; i++){
   await Dispatcher.BeginInvokenew Action(() =>
   {
      for (int j = 0; j < 1000; j++) { //add thousand items at once
         MyModel.MyCollection.Add(items[i * 1000 + j])
   });
}

正在循环遍历结果集并使用Dispatcher。调用一个好的解决方案,还是有更好的方法?

在现代应用程序中使用Dispatcher.BeginInvoke从来没有一个好的理由。

在你的情况下,因为你已经使用TPL数据流,你可以只是改变你的ActionBlockTransformManyBlock,并将其链接到一个单独的ActionBlock,在UI线程上执行。比如:

var _getRowsBlock = new TransformManyBlock<Func<Task<IEnumerable<TRow>>>, TRow>(
    action => action());
var _updateUiBlock = new ActionBlock<TRow>(row =>
{
  MyModel.MyCollection.Add(new MyClass() { MyInt = i });
}, new ExecutionDataflowBlockOptions
{
  TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(),
});
_getRowsBlock.LinkTo(_updateUiBlock, new DataflowLinkOptions { PropagateCompletion = true });
_block.Post(async () =>
{
  await Task.Delay(1000); // Perhaps Long database read
  return result.Rows; // return the database result set
});

我想一次加载一行到GUI,因为即使启用了虚拟化,似乎如果我一次更新整个可观察集合,GUI将挂起,直到它可以渲染整个数据网格。

好吧,那么你可能在寻找错误的解决方案。我看不出一次添加一行数据会有什么帮助。如果用户界面一次添加1000000行,那么一次添加1000000行会使它变得更加沉重……

你可能不得不考虑一个解决方案,你加载1000000行到你的UI