Updating an Observablecollection in a Separate Thread

Updating an ObservableCollection in a separate thread

With the built-in ObservableCollection<T> class, you can't change the content from a separate thread if the UI is bound to the collection, it throws a NotSupportedException (but change notification for properties of collection items works fine). I wrote an AsyncObservableCollection<T> class to handle this case. It works by invoking the event handlers on the UI synchronization context

Best/cleanest strategy to update an ObservableCollection from another thread

If you are on .NET Framework 4.5 or later you can enable the collection to be accessed across multiple threads by calling the BindingOperations.EnableCollectionSynchronization method. Note that this method must be called on the UI thread:

public class FooClass
{
private readonly object _lock = new object();
public ObservableCollection<string> ItemsCollection { get; } = new ObservableCollection<string>();

internal static FooClass Instance => _Instance ?? (_Instance = new FooClass());
private static FooClass _Instance;

private FooClass()
{
BindingOperations.EnableCollectionSynchronization(ItemsCollection, _lock);
}

private void _UpdateTheCollectionFromAnotherThread()
{
List<string> items = new List<string>();
ItemsCollection.Clear();
foreach (string item in items)
{
ItemsCollection.Add(item);
}
}
}

If the FooClass may be created on a background thread, you might still improve your code a bit by not repeating yourself:

public class FooClass
{
public ObservableCollection<string> ItemsCollection { get; } = new ObservableCollection<string>();

internal static FooClass Instance => _Instance ?? (_Instance = new FooClass());
private static FooClass _Instance;

private FooClass() { }

private void _UpdateTheCollectionFromAnotherThread()
{
List<string> items = new List<string>();

if (System.Windows.Application.Current.Dispatcher.CheckAccess())
ClearCollection(items);
else
System.Windows.Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => ClearCollection(items)));
}

private void ClearCollection(List<string> items)
{
ItemsCollection.Clear();
foreach (string item in items)
{
ItemsCollection.Add(item);
}
}
}

Updating an ObservableCollection from another thread

You need to set the synchronization context correctly using

ObserveOn(SynchronizationContext.Current)

See this blog post

http://10rem.net/blog/2011/02/17/asynchronous-web-and-network-calls-on-the-client-in-wpf-and-silverlight-and-net-in-general

for an example.

Here's an example that works for me:

<Page.Resources>
<ViewModel:ReactiveListViewModel x:Key="model"/>
</Page.Resources>

<Grid DataContext="{StaticResource model}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="Start" Command="{Binding StartCommand}"/>
<ListBox ItemsSource="{Binding Items}" Grid.Row="1"/>
</Grid>

public class ReactiveListViewModel : ViewModelBase
{
public ReactiveListViewModel()
{
Items = new ObservableCollection<long>();
StartCommand = new RelayCommand(Start);
}

public ICommand StartCommand { get; private set; }

private void Start()
{
var observable = Observable.Interval(TimeSpan.FromSeconds(1));

//Exception: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
//observable.Subscribe(num => Items.Add(num));

// Works fine
observable.ObserveOn(SynchronizationContext.Current).Subscribe(num => Items.Add(num));

// Works fine
//observable.ObserveOnDispatcher().Subscribe(num => Items.Add(num));
}

public ObservableCollection<long> Items { get; private set; }
}

What's the best way to update an ObservableCollection from another thread?

If MVVM

public class MainWindowViewModel : ViewModel {

private ICommand loadcommand;
public ICommand LoadCommand { get { return loadcommand ?? (loadcommand = new RelayCommand(param => Load())); } }

private ObservableCollection<ViewModel> items;
public ObservableCollection<ViewModel> Items {
get {
if (items == null) {
items = new ObservableCollection<ViewModel>();
}
return items;
}
}

public void Load() {
BackgroundWorker bgworker = new BackgroundWorker();
bgworker.WorkerReportsProgress = true;
bgworker.DoWork += (s, e) => {
for(int i=0; i<10; i++) {
System.Threading.Thread.Sleep(1000);
bgworker.ReportProgress(i, new List<ViewModel>());
}
e.Result = null;
};
bgworker.ProgressChanged += (s, e) => {
List<ViewModel> partialresult = (List<ViewModel>)e.UserState;
partialresult.ForEach(i => {
Items.Add(i);
});
};
bgworker.RunWorkerCompleted += (s, e) => {
//do anything here
};
bgworker.RunWorkerAsync();
}
}

Why isn't it possible to update an ObservableCollection from a different thread?

First...I feel your pain. The Ui thread restriction can be a pain...

Why can't you update a Ui Element from
a thread other than the one it was
created on ?

My question is, why there is such an
exception?

Well in a nutshell, history. Windows has been around a while and the way some parts of the Gui work are embedded in technologies such as COM and the like....so changing it is not trivial...would be very easy to break something. There are many other issues I'm sure...but somebody smarter than me would need to explain them. I believe the WPF team really wanted to remove this restriction and they worked at it pretty hard...in the end I think the number of core OS changes need was unworkable...so they moved on....rats.

Why wasn't it possible to allow
collection updates from any thread?

Is was and is possible... Making something thread-safe always costs some in performance and add complexity. In most cases the application doesn't call for multi thread access. It is important to understand that, for the most part, Microsoft plays by the same rules we do and the same restrictions. If they had made the ObservableCollection thread-safe ...they would have used the same tools we have...locks, monitors, etc. They cannot break the Ui thread rule any more than we can...no magic...same rules.

I know there are workarounds, so my
question is not how to avoid the "This
type of CollectionView does not
support changes to its
SourceCollection from a thread
different from the Dispatcher thread"
exception.

There are no workarounds...There is nothing to workaround. The ObservableCollection is broken..it is just not thread-safe. You must make it, or access to it, thread-safe. This is the same for anything that is not thread-safe...if you need it to be thread-safe then make it so. If you are using threads then you know about locks and such...use them..that is what they are for.

...block UI update when
ObservableCollection is changed from
other threads.... it will always work,
at least if locks are used
properly....

If locks are used properly...Exactly ! Again, Microsoft could have put these locks in but they didn't and for very good reasons. You can put the locks in. Or you use other tactics that will give you thread-safe access....lots of options.

The Task Parallel Library in .net4.0 provides some new tools for solving these problems. Being able to set a context for a task or a thread is particularly useful...

  // get the Ui thread context
_uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

Action DoInBackground = new Action(() =>
{
/*...In the background...
...process some data for an ObservableCollection...*/
});

Action DoOnUiThread = new Action(() =>
{
/*...On the UI thread...
...read/write data to an ObservableCollection...*/
});

// start the background task
var t1 = Task.Factory.StartNew(() => DoInBackground());
// when t1 is done run t1..on the Ui thread.
var t2 = t1.ContinueWith(t => DoOnUiThread(), _uiScheduler);

Don't think about the thread affinity requirements of Ui Elements as something to work around....it is just the way it works.

C# and .Net have many tools that you can use that make threading a little less of a nightmare. Use them..they can be fun.

I'm going for a smoke.

How do I update an ObservableCollection via a worker thread?

Technically the problem is not that you are updating the ObservableCollection from a background thread. The problem is that when you do so, the collection raises its CollectionChanged event on the same thread that caused the change - which means controls are being updated from a background thread.

In order to populate a collection from a background thread while controls are bound to it, you'd probably have to create your own collection type from scratch in order to address this. There is a simpler option that may work out for you though.

Post the Add calls onto the UI thread.

public static void AddOnUI<T>(this ICollection<T> collection, T item) {
Action<T> addMethod = collection.Add;
Application.Current.Dispatcher.BeginInvoke( addMethod, item );
}

...

b_subcollection.AddOnUI(new B());

This method will return immediately (before the item is actually added to the collection) then on the UI thread, the item will be added to the collection and everyone should be happy.

The reality, however, is that this solution will likely bog down under heavy load because of all the cross-thread activity. A more efficient solution would batch up a bunch of items and post them to the UI thread periodically so that you're not calling across threads for each item.

The BackgroundWorker class implements a pattern that allows you to report progress via its ReportProgress method during a background operation. The progress is reported on the UI thread via the ProgressChanged event. This may be another option for you.

How to update only a property in an observable collection from thread other than dispatcher thread in WPF MVVM?

Either use the dispatcher to call the Add method on the UI thread:

Application.Current.Dispatcher.BeginInvoke(() => DailyEmployees.Add(em)); 

Or call the BindingOperations.EnableCollectionSynchronization method on the UI thread to enable the collection to be used on multiple threads:

public class DisplayViewModel
{
private readonly ObservableCollection<Employee> _dailyEmployees = new ObservableCollection<Employee>();
private readonly object _lock = new object();

public ObservableCollection<Employee> DailyEmployees
{
get { return _dailyEmployees; }
}

public DisplayViewModel()
{
System.Windows.Data.BindingOperations.EnableCollectionSynchronization(_dailyEmployees, _lock);
OnStatusChanged += DisplayViewModel_OnStatusChanged;
}

//invoked in other thread
private void DisplayViewModel_OnStatusChanged(object sender, EventArgs e)
{
var d = sender as Employee;
if (d == null)
return;
var em = DailyEmployees.FirstOrDefault(a => a.Name == d.Name);

if (em == null)
{
DailyEmployees.Add(em);
}
else
{
em.Status = d.Status;
}
}
}

Also note that the Employee class should implement the INotifyPropertyChanged interface and raise the PropertyChanged event for the Status property if you want the value of this one to be reflected in the view when you set it.

How to modify an ObservableCollection from a different thread in .Net Standard 2.0?

You could simply pass the proper dispatcher to the library:

public void ThreadSafeAddItem(Item item, Dispatcher dispatcher)
{
dispatcher.Invoke(()=> { AddItem(item); } );
}

But since .Net standard 2.0 does not have Dispatcher class you need a delegate to do that in WPF project.

public event Action<Item> ItemAdded;
public void ThreadSafeAddItem(Item item)
{
if(ItemAdded!=null) ItemAdded(item);
}

And in your WPF:

 Library.ItemAdded += (item) => {
Dispatcher.Invoke(()=>Library.ObservCollection.Add(item));
};

In another thread:

 Library.ThreadSafeAddItem(item);

Am I doing something that I shouldn't do by using ObservableCollections here

According to MSDN:

To fully support transferring data values from binding source objects to binding targets, each object in your collection that supports bindable properties must implement an appropriate property changed notification mechanism such as the INotifyPropertyChanged interface.

That possibly means that every bindable property needs this mechanism as well, creating a huge overhead in your app.

It is recommended to use a thread-safe approach for the library (like a POCO Model) and instead address these bindings in another tier like ViewModel

Using ObservableCollection on a new thread

The approach of multi-threading within any 'heavy' work within an application you want to keep responsive is the right way of thinking about it, so you're on the right track.

However, whilst you are creating and working with other threads here, you are still relying on the Dispatcher too much. Consider that, with multi-threading here, your process should be as follows:

  1. Do the heavy lifting on a separate thread.
  2. Once done, ask the Dispatcher to update the UI as necessary.

This mimimises the load on the Dispatcher.

Have you considered using Tasks? They are great from a 'clean code' point of view, but are applicable here because with Task Continuation, you can chain Tasks together to invoke the relevant code on the UI once the heavy work is complete on its thread.

Take a look at the answer here for a good start.

I'll happily provide a more detailed example if you need it after that.

EDIT: As mentioned in another answer, BackgroundWorker is just as effective here... and the end result is exactly the same from a threading perspective. I just like the Task syntax!

EDIT: Just thought I'd provide some code. I'll avoid continuation for simplicity at the moment. Consider the following method which would do your heavy lifting:

    public void HeavyLifting(Action<List<Items>> callback)
{
Task<List<Items>> task = Task.Factory.StartNew(
() =>
{
var myResults = new List<Items>();

// do the heavy stuff.

return myResults;
});

callback.Invoke(task.Result);
}

Then for your UI (in your ViewModel for example), you can both call and handle the callback. When needed, call the 'heavy lifting' and pass in your callback:

HeavyLifting(this.HandleHeavyLiftingCompleted);

And then, you method you pass as the callback gets executed upon task completion. Note that here is where I'm asking the Dispatcher to do the work:

private void HandleHeavyLiftingCompleted(List<Items> results)
{
this._uiDispatcher.BeginInvoke(
new Action(() => { this.MyItems = new ObservableCollection<Items>(results); }));
}

Note that in this case the UI work involved is updating an ObvservableCollection to which I'm bound from the View. For the example here I'm using a random 'Item' object that can be whatever you like!

I'm using Cinch, and therefore relying on a service to obtain the relevant Dispatcher (which you see as this._uiDispatcher here). In your case you can get a reference to it using the methods mentioned in other questions here.

Also, if you have the time to read, there is some great information here regarding the WPF threading model.



Related Topics



Leave a reply



Submit