0

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);
    }
timmkrause
  • 3,367
  • 4
  • 32
  • 59
  • 1
    FYI, `Microsoft.Bcl.Async` works on XP, so you *can* use `async`/`await` on .NET 4.0. – Stephen Cleary Dec 16 '13 at 14:13
  • It's the same problem/exception with TPL btw. However it is nice to know! – timmkrause Dec 16 '13 at 14:33
  • There isn't a quick solution for your problem. Ideally, you should be using `async` *instead of* a thread to load from the database, and you must remove items on the UI thread. If you have too many items, then you need to use virtualization. – Stephen Cleary Dec 16 '13 at 14:53
  • What you are mentioning is exactly what is NOT working. Doing the refresh async or in a different thread is the same (concerning this problem). The binding happens in another (non UI) thread and that results in non deletable items. If I simply remove the async or thread stuff it works fine but animations stop and start after the actual work is done. – timmkrause Dec 16 '13 at 15:16
  • If you are using proper `async` methods, then there is no "other thread" used for the binding; it will be on the UI thread, and the animations will not be blocked. If you have `async` code please do post it, since that would be the best solution. – Stephen Cleary Dec 16 '13 at 15:39
  • 'public async void RefreshItems()' and replaced the thread wrapping with 'await Task.Factory.StartNew(() => { /* The code... */ });' -> And I experience the same problem. – timmkrause Dec 16 '13 at 16:31
  • Right; that's not the correct `async` approach. For a proper `async` method you need to use EF6 or something like that to asynchronously load from the database, then `_db.GetItems()` becomes `await _db.GetItemsAsync()`, and `void RefreshItems()` becomes `async Task RefreshItemsAsync()`, and there's no need for another thread. – Stephen Cleary Dec 16 '13 at 16:38
  • Thought I can make everything async that way. Regardless of an underlying async/await architecture. '_db' is a non async repository. What to do then? – timmkrause Dec 16 '13 at 21:00
  • I've posted an answer based on your problem and the additional information in the comments. – Stephen Cleary Dec 16 '13 at 21:23

1 Answers1

2

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Awesome! I got it working now but I have some questions left. Would you mind answer me those last ones? 1. I had to use Task.Factory.StartNew() again because Task.Run() is not available. Seems to be a little difference to the native framework implementation? 2. First I used async void which I know to be used carefully (or avoided completely). The reason: I had to call the method in the constructor. Now I refactored it to async Task again and invoke RefreshItemsAsync().Wait(). Is that an acceptable approach? 3. Btw: The only difference of your async code (to my try) is the scope (smaller). – timmkrause Dec 17 '13 at 08:49
  • In `Microsoft.Bcl.Async`, the `Run` is on the `TaskEx` type. [I recommend that you use `Task.Run` over `StartNew`.](http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html) Constructors aren't a sufficient reason to use `async void` or `Wait` IMO; check out my post on [`async` constructors](http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html) for alternatives. – Stephen Cleary Dec 17 '13 at 13:07