Reputation: 3161
If I use an ActionBlock
for a database call, and need to update the GUI (perhaps an ObservableCollection
). Is looping through the result set and using the Dispatcher.BeingInvoke
a good solution, or is there a better way?
I wanted to load in one row at a time to the GUI, since even with virtualization enabled it seemed like if I updated the entire observable collection at once the GUI would hang till it could render the whole datagrid.
Some sample code which simulates the situation:
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
);
}
});
Upvotes: 0
Views: 601
Reputation: 27338
If you add call Dispather.BeginInvoke inside loop, then you update you UI 100k times. Ideally I would to this:
//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);
));
if your virtualization really works, it should be no problem.
in order to avoid adding large number in UI thread, you could split it to smaller portions of data:
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])
});
}
Upvotes: 1
Reputation: 456407
Is looping through the result set and using the Dispatcher.BeingInvoke a good solution, or is there a better way?
There's never a good reason to use Dispatcher.BeginInvoke
in a modern application.
In your case, since you're already using TPL Dataflow, you can just change your ActionBlock
to a TransformManyBlock
, and link it to a separate ActionBlock
that executes on the UI thread. Something like:
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
});
I wanted to load in one row at a time to the GUI, since even with virtualization enabled it seemed like if I updated the entire observable collection at once the GUI would hang till it could render the whole datagrid.
Well, then you're probably looking at the wrong solution. I don't see how adding data one row at a time would help. If the UI is hammered adding 1000000 rows at once, then adding 1000000 rows one at a time would hammer it even more...
You may have to consider a solution where you don't load 1000000 rows into your UI.
Upvotes: 0