views:

48

answers:

1

I am trying to improve the performance of a DataGridView hooked up to a collection of custom data objects. The collection doesn't actually change size once initialized (i.e., no data objects are added or removed once the list is loaded on start-up), but the properties of the data objects dynamically change, and the grid is basically supposed to be a real-time display of those data objects.

This collection, which implements IBindingListView, is data-bound to the DataGridView using standard Visual Studio designer support, resulting in a BindingSource in the normal role of proxy between controls and their data source. A couple other controls are also hooked up to the collection class, using some summary properties that are exposed (e.g., TotalCountWithPropertyXXX)

Previously, the code on the form actually used a timer to call BindingSource.ResetBindings(false); in order to update the bound controls. I thought this was less than ideal, so I implemented basic INotifyPropertyChanged support on the data objects themselves in order to have object-level prop changes bubble up to the BindingSource and ultimately have the DataGridView automatically update itself to display the new values of the edited properties (and obviously ripped out the calls to BindingSource.ResetBindings).

My implementation of INotifyPropertyChanged was pretty stock: just fired the event from the setter of the property accessor with a string value containing the respective property name, e.g.:

public event PropertyChangedEventHandler PropertyChanged;

public string Name
{
    get { return _name; }
    set
    {
        if (value != _name)
        {
            _name = value;
            NotifyPropertyChanged("Name");
        }
    }
}

private void NotifyPropertyChanged(String info)
{
    PropertyChangedEventHandler localOnPropChanged = OnPropertyChanged;
    if (localOnPropChanged != null)
    {
        localOnPropChanged(this, new PropertyChangedEventArgs(info));
    }
}

This worked OK for me, although with several hundred to a couple thousand data objects in our list, I did notice a little sluggishness in the grid updating. The death blow (from a perf perspective) came when testing some existing functionality to bulk update the data bojects -- i.e., there's a button on the form to flip one of the boolean properties for all the data objects in the list. When this button's event handler fires, it enumerates through the entire data object list and sets the flag to true/false and the UI just hangs for 8-10 seconds before coming back to life and marking all the objects with the updated boolean (using a DataGridViewCheckBoxColumn).

My first thought was to disable the BindingSource's ListChanged event from firing (RaiseListChangedEvents = false;) while the button's event handle was updating all the data objects, but this made no noticable difference.

We have some simple tracing in place, so I looked at the timestamps and the log seems to shows that basically the entire duration of the hang is spent firing the property change notifications on the the thousand or so data objects I have in my unit test environment.

If I comment out the event notification code in the property set for the boolean propery being set by the aforementioned bulk-edit button, the hang goes away entirely and the grid is snappy again -- but I sacrifice the grid updates (i.e., the checkbox column cells do not update.)

I did some searching and I didn't find much mention of implementing INotifyPropertyChanged being a significant perf hit for the number of objects I'm dealing with, so I'm not sure if we have bugs in our code which need to be fixed -- e.g., my inefficient implementation of INotifyPropertyChanged, perhaps bad implementation of IBindingListView in our collection class, or something dumb in our databinding -- or if I'm hitting the limit of what I can do in the DataGridView without implementing Virtual Mode.

For now, I've unhooked the INotifyPropertyChanged code I wrote and trying to invalidate the grid whenever I think we have changes to the data objects by using event handlers on CellChanged, the Button Click, etc... but I can't help feeling like I don't really understand the root perf issue here and that I might be choosing a less clean implementation strategy just because I don't get what my perf problem really is.

So I'm looking for some guidance/feedback on whether my basic approach seems OK, or I need to change implementation strategy.

TIA, Matt

+1  A: 

In our current app, there are some big grid views, and when they are sluggish I just take a few stackshots to see what's going on.

Generally what is taking all the time is notifications gone wild. Where possible, it is better not to try to intensely keep data consistent with notifications, but rather to be able to tolerate some temporary inconsistency and clean it up occasionally in some kind of once-over pass.

Mike Dunlavey
Thanks Mike. Good to know that the proliferation of notifications is a real cause. What is the best way to take a stackshot?
Matt
Not sure if you meant to use a profiling tool or just pause in the debugger and look at the threads' callstacks, but I did the latter and have managed to get enough notifications and costly operations removed that I've got acceptable performance now. Thanks!
Matt
@Matt: You got it! Good work.
Mike Dunlavey