Prev topic: .Net Grid tutorial (Part5: Threadsafety)
Next topic: .Net Grid tutorial (Part7: Appearance and painting)
Initially ThreadSafeBindingList<T> class was created to protect collection in multi-threading mode. However, when Dapfor’s developers worked with actual applications, they faced a serious performance issue with standard binding list working with objects implementing INotifyPropertyChanged interface. They have discovered reasons for low performance of BindingList<T> with such objects and modified ThreadSafeBindingList<T> to significantly improve application performance while receiving all advantages of the existing container.
Now let’s learn a little bit of history.
BindingList<T> is an implementation of IBindingList interface that notifies subscribers of changes in collection. Usual subscribers are grids, lists and other graphical controls that work with data collections. Such controls get notifications via IBindingList.ListChanged event. This event is fired when data is added to or removed from the collection and upon major changes of the collection. For this reason, ListChangedEventArgs specifies event that occurred at the collection and the grid rebuilds its internal data presentation structure basing on this information. There can be the following ListChangedType: ItemAdded, ItemChanged, ItemDeleted, ItemMoved, PropertyDescriptorAdded, PropertyDescriptorChanged, PropertyDescriptorDeleted, Reset. There are simple types – adding/removing and complete rebuilding of data. However, ItemChanged reason is very interesting. In this case instead of internal changes of the collection (adding, removing, etc) the binding list reports changes of data objects. For this purpose the binding list subscribes to each object implementing INotifyPropertyChanged interface by adding a handler to INotifyPropertyChanged.PropertyChanged event. By getting a notification from such object, the binding list doesn’t manipulate its internal data structure but transforms the received notification into its own IBindingList.ListChanged event with ItemChanged value and PropertyDescriptor corresponding to the field of notifying object.
The following code can be used to get PropertyDescriptor from PropertyChangedEventArgs:
C# | Copy |
---|---|
object dataObject = ...; string propertyName = "someProperty"; PropertyDescriptor property = TypeDescriptor.GetProperties(dataObject)[propertyName]; |
Performance here is not very high, but it is not critical as there is a more serious issue. BindingList<T> contains internal cache that theoretically should improve performance. This cache compares objects that fired INotifyPropertyChanged notifications. When subsequent notifications are received from the same object, this cache works perfectly. However, when they are fired by different objects, the binding list tries to calculate a new index of object in the collection that has fired the notification. For this purpose the binding list calls List.IndexOf(object) triggering linear enumeration of all collection elements to find the required element's index. This means that upon every notification from objects implementing INotifyPropertyChanged, the binding list enumerates all objects in the collection. This problem is well demonstrated with the following code:
C# | Copy |
---|---|
[Test] public void BindingListPerfTest() { //Populate binding list with 100 000 objects Random r = new Random(); BindingList<SomeClass> bindingList = new BindingList<SomeClass>(); for(int i = 0; i < 100000; ++i) { bindingList.Add(new SomeClass()); } //Measure time, required for 10000 notifications DateTime dt = DateTime.UtcNow; for (int i = 0; i < 10000; ++i) { bindingList[r.Next() % bindingList.Count].SomeValue = r.Next(); } //10000 notification take about 5 seconds! TimeSpan ts = DateTime.UtcNow - dt; //The same example with ThreadSafeBindingList will take 4 milliseconds } //Sample class public class SomeClass : INotifyPropertyChanged { private int _someValue; public int SomeValue { get { return _someValue; } set { _someValue = value; if(PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("SomeValue")); } } } public event PropertyChangedEventHandler PropertyChanged; } |
The second serious issue of the binding list is that upon getting a notification from an object with non-existing property, the binding list generates ListChanged event with Reset cause making the grid to rebuild all its contents.
C# | Copy |
---|---|
class MyDataObject : INotifyPropertyChanged { private string _lastPrice; [Field("Price")] public string LastPrice { get { return _lastPrice; } set { if (_lastPrice != value) { _lastPrice = value; if (PropertyChanged != null) { //This code will cause Reset in BindingList because MyDataObject doesn't contain "Price" property PropertyChanged(this, new PropertyChangedEventArgs("Price")); } } } } public event PropertyChangedEventHandler PropertyChanged; } |
From theory to practice
It is doubtful that binding list’s subscription to INotifyPropertyChanged is useful as it doesn’t process these notifications but only transforms them to IBindingList.ListChanged format. It doesn’t make any internal manipulations with its own data.
ThreadSafeBindingList<T> container is only a wrapper for BindingList<T> and fully replicates its functionality. When Dapfor’s developers discovered the cause for low performance, they blocked BindingList<T> subscription to INotifyPropertyChanged events solving two problems at once. The first problem was incorrect organization of internal cache, and the second problem was getting notifications from objects with non-existing properties. Still, ThreadSafeBindingList<T> doesn’t block firing notifications upon adding/removing items just as the standard container.
Architecture of the .Net Grid enables it to subscribe to events of objects implementing INotifyPropertyChanged interface without regard to the adding method. When the grid receives notifications from these objects, it performs thread synchronization, data update, sorting, regrouping, filtering and highlighting. Therefore, blocking BindingList<T> subscription to INotifyPropertyChanged interface doesn’t impact application functionality and significantly improves their performance.
Thread safety
BindingList<T> is not a thread-safe container. As said above, the main purpose of creating ThreadSafeBindingList<T> was to protect the binding list from competitive access by multiple application threads.
ThreadSafeBindingList<T> is just a wrapper that contains the BindingList<T> and locks its internal method. ThreadSafeBindingList<T> implementation prevents occupation of synchronizing object at the time when notification is fired. It is especially important to avoid potential deadlock problems when two synchronizing objects are taken by the opposite threads.
To summarize the above, Dapfor recommends using ThreadSafeBindingList<T> instead of standard BindingList<T>. This ensures thread protection for the application and significantly improves application performance by optimizing usage of the standard container.