在.NET中实现数据绑定的线程安全集合

时间:2022-10-22 10:38:45

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.

我有一个Windows窗体应用程序,它显示一个表单,其中DataGridView绑定到继承BindingList的自定义集合。我正在使用BindingSource / DataSource机制进行数据绑定。表单是一个监视器,显示集合中包含的状态信息。集合的每个元素表示许多子线程之一的状态信息。

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:

我正在使用SynchronizationContext方法来确保我的集合中的ListChanged事件与UI线程同步,并且不会发生跨线程问题。但是,似乎多个线程仍然可以同时使用该集合。这会导致数据绑定问题。下面是一个发生异常的示例:

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)

System.ArgumentOutOfRangeException:指定的参数超出了有效值的范围。 System.Windows.Forms上System.Windows.Forms.DataGridView.GetCellAdjustedDisplayRectangle(Int32 columnIndex,Int32 rowIndex,Boolean cutOverflow)的System.Windows.Forms.DataGridView.GetCellDisplayRectangle(Int32 columnIndex,Int32 rowIndex,Boolean cutOverflow)中的参数名称:rowIndex System.Windows.Forms上System.Windows.Forms.DataGridView.DataGridViewDataConnection.ProcessListChanged(ListChangedEventArgs e)的System.Windows.Forms.DataGridView.OnCellCommonChange(Int32 columnIndex,Int32 rowIndex)中的.DataGridView.InvalidateCellPrivate(Int32 columnIndex,Int32 rowIndex) System.WindView.Forms上System.Windows.Forms.CurrencyManager.List_ListChanged(Object sender,ListChangedEventArgs e)的System.Windows.Forms.CurrencyManager.OnListChanged(ListChangedEventArgs e)中的.DataGridView.DataGridViewDataConnection.currencyManager_ListChanged(Object sender,ListChangedEventArgs e) System.Windows.Forms.BindingSource.InnerList_ListChanged中的.BindingSource.OnListChanged(ListChangedEventArgs e)(对象发送者,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.

这让我相信在初始ListChanged事件被引发并由UI线程处理后,集合再次被更改。

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.

所以,我的问题是如何使我的集合不仅是线程安全的,而且是阻塞的,以便在允许另一个更改之前,在ListChanged事件之后UI可以刷新?排队ListChanged事件是不够的,我需要阻止导致ListChanged事件被触发的操作。例如,如果我更改元素的属性然后引发ListChanged事件(ListChangedType = ItemChanged),则阻止尝试将项添加到集合的另一个线程,直到ListChanged事件处理程序返回。

Any ideas?

2 个解决方案

#1


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.

将视图绑定到集合的副本,然后适当地控制对集合的访问:确保线程在lock()部分中进行更新,并确保使用同一对象上的lock()完成复制。

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.

只是一个想法。

#2


Try ConcurrentBag<T>

#1


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.

将视图绑定到集合的副本,然后适当地控制对集合的访问:确保线程在lock()部分中进行更新,并确保使用同一对象上的lock()完成复制。

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.

只是一个想法。

#2


Try ConcurrentBag<T>