Bound items in extra thread not deletable from dispatcher thread

68 views Asked by At

RefreshItems is called from the constructor of the ViewModel and when the user wishes it (RefreshCommand on button click).

Delete is also bound to a DeleteCommand.

I want to refresh the items within a new thread because of some animations which aren't fluid otherwise.

So the binding does not happen on the thread of the dispatcher but the deletion does and the deletion throws an exception (see code).

(TPL (async/await) is no option since XP has to be supported.)

    public void RefreshItems()
    {
        new Thread(new ThreadStart(() =>
        {
            IsRefreshing = true;

            var items = _db.GetItems();

            var itemsCollectionView = CollectionViewSource
                .GetDefaultView(new ObservableCollection<ItemType>(items));

            Items = itemsCollectionView;

            IsRefreshing = false;
        })).Start();
    }

    private void Delete(ItemType item)
    {
        _db.DeleteItem(item);

        var items = (ObservableCollection<ItemType>)Items.SourceCollection;

        // InnerException: NotSupportedException
        // Message: This type of CollectionView does not support changes
        //          to its SourceCollection from a thread different from
        //          the Dispatcher thread.
        items.Remove(item);
    }
1

There are 1 answers

2
Stephen Cleary On BEST ANSWER

I find it works best to treat data-bound items as though they were part of the UI. So, nothing that is data-bound should be accessed from a background thread.

Ideally, your database access would be using something like EF6 which supports asynchronous methods. However, since you do not have an async database layer, you can use a "fake asynchronous" approach to push the (synchronous) database work onto a background thread:

public async Task RefreshItemsAsync()
{
    IsRefreshing = true;

    var items = await Task.Run(() => _db.GetItems());

    var itemsCollectionView = CollectionViewSource
        .GetDefaultView(new ObservableCollection<ItemType>(items));

    Items = itemsCollectionView;

    IsRefreshing = false;
}

private async Task DeleteAsync(ItemType item)
{
    await Task.Run(() => _db.DeleteItem(item));

    var items = (ObservableCollection<ItemType>)Items.SourceCollection;

    items.Remove(item);
}

However, this does require your database layer to be thread-agnostic. If it's caching some db connection or something that is tied to a specific thread then this approach won't work.