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

时间:2015-06-01 15:03:43

标签: c# wpf .net-4.5

如果我使用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
                    );
                }
            });

2 个答案:

答案 0 :(得分:1)

如果你在循环中添加调用Dispather.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])
   });
}

答案 1 :(得分:0)

  

循环遍历结果集并使用Dispatcher.BeingInvoke是一个很好的解决方案,还是有更好的方法?

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

在您的情况下,由于您已经在使用TPL数据流,因此您只需将ActionBlock更改为TransformManyBlock,并将其链接到在UI上执行的单独ActionBlock线。类似的东西:

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中。