views:

411

answers:

3

I have a Windows Forms application that displays a form with a DataGridView bound to a custom collection that inherits BindingList. I'm using the BindingSource / DataSource mechanism for data-binding. The form is a monitor that displays status information contained in the collection. Each element of the collection represents status information for one of many child threads.

I am using the SynchronizationContext approach to make sure that the ListChanged event from my collection is synchronized with the UI thread and no cross-threading issues occur. However, it appears that it is still possible for multiple threads to work with the collection at the same time. This causes problems with data-binding. Below is an example of an exception that has occurred:

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. Parameter name: rowIndex at System.Windows.Forms.DataGridView.GetCellDisplayRectangle(Int32 columnIndex, Int32 rowIndex, Boolean cutOverflow) at System.Windows.Forms.DataGridView.GetCellAdjustedDisplayRectangle(Int32 columnIndex, Int32 rowIndex, Boolean cutOverflow) at System.Windows.Forms.DataGridView.InvalidateCellPrivate(Int32 columnIndex, Int32 rowIndex) at System.Windows.Forms.DataGridView.OnCellCommonChange(Int32 columnIndex, Int32 rowIndex) at System.Windows.Forms.DataGridView.DataGridViewDataConnection.ProcessListChanged(ListChangedEventArgs e) at System.Windows.Forms.DataGridView.DataGridViewDataConnection.currencyManager_ListChanged(Object sender, ListChangedEventArgs e) at System.Windows.Forms.CurrencyManager.OnListChanged(ListChangedEventArgs e) at System.Windows.Forms.CurrencyManager.List_ListChanged(Object sender, ListChangedEventArgs e) at System.Windows.Forms.BindingSource.OnListChanged(ListChangedEventArgs e) at System.Windows.Forms.BindingSource.InnerList_ListChanged(Object sender, ListChangedEventArgs e)

This makes me believe that the collection was changed again after the initial ListChanged event was raised and being handled by the UI thread.

So, my question is how to make my collection not only thread-safe but blocking so that the UI can refresh after a ListChanged event before another change is allowed? It's not enough to queue the ListChanged events, I need to block the operation that led to the ListChanged event being fired. For example, if I change the property of an element then raised the ListChanged event (ListChangedType = ItemChanged), another thread that is trying to add an item to the collection is blocked until the ListChanged event handler(s) return.

Any ideas?

A: 

Bind the view to a copy of the collection, then control access to the collection appropriately: ensure the threads do their updates within a lock() section, and ensure the copying is also done using a lock() on the same object.

This would also let you queue write requests so that the writers don't block, they just send a message with their update, which might be good for performance reasons.

Just an idea.

Rory
+1  A: 

But if I bind the UI to a copy, then I have to rebind each time the original changes - to a new copy. That kind of defeats to point of binding in the first place, doesn't it. And aren't I adding a significant amount of overhead?

A: 

Try ConcurrentBag<T>

mumtaz