BindableCollection - ArgumentOutOfRangeException

Dec 3, 2010 at 2:48 PM
Good day,

I've encountered a situation that I think the community should know about as it took me a while to figure out the issue.

I am in the process of creating an app based upon the hybrid example in the docs.

The app has a view model that has a property of type bindableCollection.  This collection is populated OnViewLoaded via an async task that calls a web service.  

On the initial load of the view model, the process works as expected.  I then click to a different 'part' of the application (ex: orders) and then click back to the 'part' of the application that has this view model.

The application then throws the ArgumentOutOfRangeException inside the BindableCollection.cs class in the InsertItemBase method, with the index parameter being outside the range of the actual collection size.

The issue appears to be as follows
1) the 'load' method is called within a System.Threading.Task and it calls two methods on the BindableCollection in succession 
* prop.Clear 
* prop.AddRange(newlyRetrievedData) 
2) internal to each BindableCollection method, it calls Execute.OnUIThread which does a BeginInvoke on the dispatcher
3) the actual 'Clear' method (inside the beginInvoke) has not yet been executed by the time the prop.AddRange method is started. 
* the AddRange method still thinks that the count is > 0 and tries to add items to the end of the underlying list (again - thru the Dispatcher BeginInvoke 
4) the actual 'Clear' method inside the beginInvoke then proceeds and clears the underlying collection
5) the actual 'AddRange' method inside the beginInvoke then proceeds and fails as it tries to add an item with an invalid index.

The way that I worked around the issue was to change the 'load' method to have the bindableCollection methods inside an Execute.OnUIThread 

Execute.OnUIThread(() => prop.Clear; prop.AddRange(...));

Hopes this helps others who might encounter this situation.

James

Dec 3, 2010 at 3:09 PM

As you could verify, the BindableCollection<T> performs modification and notification on the UI thread by dispatching the call asynchronously.

This means that doing something like this

BindableCollection<object> collection = new BindableCollection<object>();
collection.Add(new object());
System.Diagnostics.Debug.WriteLine(collection.Count);
instead of outputting '1' is most likely to report '0' if the code is not executed by the Dispatcher.Thread, due to the asynchronous nature of Dispatcher.BeginInvoke.

This means that you are guaranteed that the order of operations executed over the BindableCollection is maintained (e.g. AddRange will be executed after Clear) since the Dispatcher takes care of it, but you are not guaranteed that such operations have been executed in the caller code, unless it is executed in a synchronized context (i.e. on the Dispatcher).

So, the collection behaviour is indeed correct, but can be quite tricky if you don't know exactly how the collection (or better the Dispatcher) works. Nice job pointing that out! :)

Feb 17, 2011 at 8:43 AM

I also had the problem which James described. I was using an old build of Caliburn.Micro. In the new build the Execute.OnUIThread method has changed to perform the operation synhroniously (using Dispatcher.Invoke). I wonder if this was the reason that the implementation changed?

Coordinator
Feb 17, 2011 at 1:46 PM

You are correct. Silverlight didn't have an Invoke method initially, which is why this was async to begin with. In the end, we decided to just implement a wait handle so that we could force it to by synchronous. This made things behave like people were expecting, which is always better. It also enabled us to more adequately report exceptions that were raised during invocation.