What’s wrong with those ObservableCollections and Threading?

When you are developing Apps with Xamarin.Forms you probably used ObservableCollections more than once. That’s pretty neat, because an ObservableCollection notifies the View on any changes that occur, so you don’t have to care about updating the Views.

A common use case is as follows. You have a list of items (models) that are being read from a database. The items are being transformed to ItemViewModels and added to a ObservableCollection. This ObservableCollection is bound to a ListView, which will be updated as soon as the item is being added. When you are lucky, and the whole transformation thing takes a lot of time, you will see every item being added subsequently.

ObservableCollection bound to a CollectionView

Looks awesome, you can still interact with the UI while the list is being generated. BUT .. that only works in a perfect world. There are several reasons, why creating a ListItem isn’t as easy as 1-2-3 (like in my example). Let’s say it takes 20ms or even more, to create one item. Your UI will probably freeze:

the UI blocks while filling the list

The users of your App will not like that. I don’t like it either, when an App shows such a bad behavior. But as always, it’s compilacted… The easiest (very uncommon) way to solve that problem is to even extend the time, fight fire with fire :-). Yes it’s easy to do but in my opinion bad practice.

If you ever developed with Windows Forms, you probably know of Application.DoEvents(). That gives the UI Thread time to work through the message pipe and handle keyboard presses and UI updates. There are two ways in Xamarin.Forms (probably also works in WPF) to do this, just use one of those two commands within your loop, that is adding the items to you ObservableCollection. This gives the UI enough time to redraw the screen:

await Task.Yield()
await Task.Delay(10)

   
            for (int i=0; i<100; i++)
            {
                //await Task.Delay(1);
                await Task.Yield();
                // do something that realy takes time
                DoSomething();
                Items.Add(new ItemViewModel
                {
                    Text = $"Item {i}",
                    MoreText = $"Some more text to Item {i}"
                });
            }

That works in a lot of cases, but maybe the whole process is to complex to do this. You could refactor and clean up your whole code (I would recommend that in the first place anyway). But putting the whole stuff on a new thead sound easier, and now we introduce the problem with Threading + ObservableCollection. You will probably get the following error:

The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

And guess what, it’s not a problem of the ObservableCollection, the ObservableCollection actually is not thread safe (as all non-concurrent collections in .Net), but the ObservableCollection doesn’t care about the thread you make changes from. The problem is the ListView, that the Items are bound to. It assumes, that changes on the collection are made from the MainThread and just handles the UI updates. And UI updates are, as we know, thread affine, so you can only add elements to the View when you are in the same thread as it is being created on (the main thread, also called UI thread). In the current version of Xamarin they introduced a control, that can easily handle that: CollectionView. That’s the control of choice, if you encounter the problem I mentioned above.

Always prefer the usage of CollectionView instead of ListView.

What if you can’t switch to CollectionView?

One thing you can do is to call Device.BeginInvokeOnMainThread(), but be careful with that, an invoke on the MainThread is always very expensive, you should only use it when you have no other choice. (refactoring included)

for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(500);
                    Device.BeginInvokeOnMainThread(() =>
                    {
                        Items.Add(new ItemViewModel
                        {
                            Text = $"Item {i}",
                            MoreText = $"Some more text to Item {i}"
                        });
                    });
                }

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.