async Task.Run with MVVM
Asked Answered
P

3

6

I have been playing around with the new async CTP and MVVM patterns. I have been converting an old program of mine that was using a background worker and report progress to update a collection in my model. I have converted it to something like so

TaskEx.Run(async () =>
{
  while (true)
  {
    // update ObservableCollection here
  }
  await TaskEx.Delay(500);
});

In my view I bind to my viewmodel which exposes this observable collection. However, when my collection updates I get the following Exception

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

I'm not sure what the correct way to pull the is back to the UI thread when done like this.

Preliminaries answered 10/12, 2011 at 22:8 Comment(1)
As a side note, there is an updated version of async in the Visual Studio 11 Developer Preview that you can download.Styrax
S
6

You don't have to run async methods using Task.Run(), or any other special means, just call them. And in your case, that's exactly what is causing the problem.

Given function like this:

Action f = async () =>
{
    while (true)
    {
        // modify the observable collection here
        await Task.Delay(500);
    }
};

Calling it like this from some method run on the UI thread, like an event handler:

f();

works exactly as it should. It executes the first iteration of the cycle and then returns. The next iteration is executed after 500 ms (or more, if the UI thread is busy) on the UI thread.

On the other hand, if you call it like this:

Task.Run(addNames);

it doesn't work correctly. The reason for this is that async methods try to continue in the same context as they were started (unless you explicitly specify otherwise). The first version was started on the UI thread, so it continued on the UI thread. The second version started on a ThreadPool thread (thanks to Task.Run()) and continued there too. Which is why it caused your error.

All this is done using SynchronizationContext, if one is present.

Styrax answered 11/12, 2011 at 1:25 Comment(0)
L
4

You created an ObservableCollection on the main UI thread, and are trying to update it on an asynchronous background thread, which you cannot do in WPF.

As an alternative, get the results from a background thread, then add them to the ObservableCollection on the main UI thread.

Usually my code for updating an ObservableCollection on a background thread will look something like this:

private async void LoadItems()
{
    Task<List<MyItem>> getItemsTask = Task.Factory.StartNew(GetItems);

    foreach(MyItem item in await getItemsTask)
        MyCollection.Add(item);
}

private List<MyItem> GetItems()
{
    // Make database call to get items
}
Lorin answered 14/12, 2011 at 19:42 Comment(0)
E
3

While it is true that you aren't able to update an ObservableCollection from a second thread, it is possible to create an asynchronous observable collection. This allows you to update the collection from within tasks, or on threads where the collection wasn't created.

I would post the example, but I found the information here. It's pretty slick, and I found it to be a very helpful example.

http://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/

Epistrophe answered 2/3, 2012 at 18:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.