obibenParticipantOctober 13, 2016 at 3:25 pmPost count: 28
Hi, I’ve got a window with a few DataGridControls in there. Two of them are bound to rapidly changing ObservableCollections, and seem to be causing trouble.
With the window open and nothing but add/remove/replace operations in the collection happening, the following exception can be thrown repeatedly (sometimes 20ish times in less than a second!):
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index at 0
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.Collections.Generic.List`1.get_Item(Int32 index)
at System.Collections.ObjectModel.Collection`1.System.Collections.IList.get_Item(Int32 index)
at Xceed.Wpf.DataGrid.DataGridCollectionView.ForceRefresh(Boolean sendResetNotification, Boolean initialLoad, Boolean setCurrentToFirstOnInitialLoad)
at Xceed.Wpf.DataGrid.DataGridCollectionView.ExecuteSourceItemOperation(DeferredOperation deferredOperation, Boolean& refreshForced)
at Xceed.Wpf.DataGrid.DeferredOperationManager.Process(Boolean processAll)
at Xceed.Wpf.DataGrid.DataGridCollectionViewBase.DeferRefreshHelper.ProcessDispose(DataGridCollectionViewBase collectionView)
at Xceed.Wpf.DataGrid.DataGridCollectionViewBase.DeferRefreshHelper.Dispose(Boolean disposing)
at System.Windows.Data.CollectionViewSource.ApplyPropertiesToView(ICollectionView view)
at System.Windows.Data.CollectionViewSource.EnsureView(Object source, Type collectionViewType)
at System.Windows.Data.CollectionViewSource.OnDataChanged(Object sender, EventArgs e)
at System.Windows.WeakEventManager.ListenerList`1.DeliverEvent(Object sender, EventArgs e, Type managerType)
at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args)
at System.Windows.Data.DataChangedEventManager.OnDataChanged(Object sender, EventArgs args)
at System.Windows.Data.DataSourceProvider.UpdateWithNewResult(Exception error, Object newData, DispatcherOperationCallback completionWork, Object callbackArgs)
at System.Windows.Data.DataSourceProvider.OnQueryFinished(Object newData, Exception error, DispatcherOperationCallback completionWork, Object callbackArguments)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
How can I avoid this? What could be causing it?Fawzi [Xceed]MemberOctober 13, 2016 at 7:20 pmPost count: 722Fawzi [Xceed]MemberOctober 14, 2016 at 8:00 pmPost count: 722
The problem here is not in the grid, but in the sample. What happens here is called a racing condition.
The sample application has 2 threads, the UI thread where the grid reside and a background thread. The sample uses the background thread to update the data source, but does not use any lock.
When the grid is being notified of a change, it consult the data source and update itself accordingly. In the meantime, the background thread is still running and updating the data source. At some point, the notifications the grid receives might not match with the data source’s current state and errors will occur.
To prevent these kind of issues, a lock is used. A lock is a way to communicate between threads in order to reserve or claim resources exclusively. So, when one thread has the lock, the other threads will be suspended and wait for the lock in order to continue.
In the sample, the data source is an ObservableCollection<>. An ObservableCollection implements the ICollection interface which has a ICollection.SyncRoot property. This “SyncRoot” property provides an object a thread must lock before it consult, manipulate or update the collection. This is the way to do if you want to share a collection between threads.
The Xceed’s DataGridControl will pick and lock the ObservableCollection’s SyncRoot object when it needs to access or update the collection, but if other actors who want to manipulate the collection doesn’t follow the same rules, it will not work.
If we compare this to a traffic light, it will work fine if cars engage into the intersection when the light is green and stop when it is red. It might fail badly if a single car decide to get through the intersection on a red light.
Back to your sample, the solution is to lock on the collection’s “sync root” before accessing or modifying the collection. To achieve this you may do something like:
lock( ( ( ICollection )Items ).SyncRoot )
// The code that access and updates the Items collection goes here.
Ideally, you should try to release the lock as soon as possible to give a chance to the grid to pick the lock. Doing something like this may work even though it does give much time to the grid to pick the lock.
while( true )
lock( ( ( ICollection )Items ).SyncRoot )
// … code that updates the source collection.
In this case, the lock is released and then acquired in no time. The other threads have almost no chance to acquire the lock. It may happen, but not often. It is important to do some pauses once in a while to give a chance to other threads to acquire the lock.obibenParticipantOctober 17, 2016 at 1:40 pmPost count: 28
Oh wow, 6 months in .NET and this is the first I read of a SyncRoot property. I had assumed that add/remove operations would lock the collection by default – possibly even getting the enumerator would’ve done so as well.
Well, thanks a bunch for the detailed answer: it was a trivial fix and everything is working wonders again.
- You must be logged in to reply to this topic.