How to make ObservableCollection thread-safe?
Solution 1
You can create a simple thread friendly version of the observable collection. Like the following :
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
if (CollectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
{
DispatcherObject dispObj = nh.Target as DispatcherObject;
if (dispObj != null)
{
Dispatcher dispatcher = dispObj.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
nh.Invoke(this, e);
}
}
}
with that now do a massive find & replace and change all your ObservableCollection
to MTObservableCollection
and your good to go
Solution 2
As of .net framwork 4.5 you can use native collection synchronization.
BindingOperations.EnableCollectionSynchronization(YourCollection, YourLockObject);
YourLockObject
is instance of any object e.g. new Object();
. Use one per collection.
This eliminates the need of some special class or anything. Just enable and enjoy ;)
[edit] As stated in the comments by Mark and Ed (thanks for clarifying!), this does not relieve you from locking the collection on updates as it just synchonizes the collection-view-binding and does not magically make the collection thread-safe itself. [/edit]
PS: BindingOperations
resides in Namespace System.Windows.Data
.
Solution 3
The solution Franck posted here will work in the case where one thread is adding things, but ObservableCollection itself (and List, which it's based on) are not thread-safe. If multiple threads are writing to the collection, hard-to-track-down bugs could be introduced. I wrote a version of ObservableCollection that uses a ReaderWriteLockSlim to be truly thread-safe.
Unfortunately, it hit the StackOverflow character limit, so here it is on PasteBin. This should work 100% with multiple readers/writers. Just like regular ObservableCollection, it's invalid to modify the collection in a callback from it (on the thread that received the callback).
Solution 4
You can use a ObservableConcurrentCollection class. They are in a package provided by Microsoft in the Parallel Extensions Extras library.
You can get it prebuilt by the community on Nuget: https://www.nuget.org/packages/ParallelExtensionsExtras/
Or get it from Microsoft here:
Related videos on Youtube
ilight
Updated on April 13, 2020Comments
-
ilight about 4 years
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
I am adding/removing from an ObservableCollection which is not on a UI thread.
I have a method names EnqueueReport to add to the colleciton and a DequeueReport to remove from the colleciton.
The flow of steps is as below :-
- 1.call EnqueueReport whenever a new report is requested
- call a method every few seconds to check if the report is generated (this has a foreach loop that checks the generated status of all reports in ObservableCollection)
- call DequeueReport if the report is generated
I am not much in C# libraries. Can someone please guide me on this?
-
Jakub about 10 yearsTry this one ObservableCollection and threading
-
Aron about 10 yearsWith the .net framework you cannot bind to an Observable Collection and then edit it outside the UI thread. There are a few patterns around that, including defered UI update, bulk inserts or creating a threadsafe class implementation of CollectionView.
-
Robert Fraser over 9 yearsThis is NOT thread-safe because ObservableCollection<T> is not thread safe. It might work most of the time, but if multiple threads are writing to it, you're going to get reenterancy exceptions or corrupt data sooner or later, and that can be a really hard problem to track down.
-
Franck over 9 yearsTypically you would not make an thread write/add directly into a non threaded object. You would use a progress event to update thread information as new items are being processed and ready to collect. Then the main thread can call asynchronously the insertion of item in that list. This list does asynchronous update through the dispatcher. But i don't know how you used it but i use it intensively and i constantly push and pull 2,250 to 2,525 items per seconds with 25 threads running on it. each push or pull between 90 and 101 items per seconds. It run 24/7 and still no issue.
-
Robert Fraser about 9 yearsAh OK I see... so basically you're only writing to it from one thread, just a different one from the UI.
-
Franck about 9 yearsyes only 1 thread write but can be done asynchronously, so could be call twice at the same time.I prefer this methods as i need very high performance and direct access from thread is unnecessary when it can be avoided. But your solution do work well if you wan threads to access it directly. You will loose performance when you get to high amount of item because you recreate a new list in some places and when you add item to a list the capacity in memory double each time so now it will double a second time. But If you stay in the low hundreds of item i don't see much loss.
-
jnm2 about 9 yearsWow, you went all out on this! It's perfect. Thanks.
-
Robert Fraser about 9 years@jnm2 - I think the version I put up before didn't compile; uploaded a new one that doesn't reference any internal utility functions.
-
Robert Fraser about 9 yearsThe list is cleared before being recreated so it won't double in capacity again. If new items are added, it will double in capacity to accommodate them, of course. Also, Since it uses a WeakReference there shouldn't be any huge memory cost. If you have 30 threads enumerating it at once and 100,000 items it might be a problem, but as long as only the UI thread is accessing it, there will only be one snapshot list at any time.
-
jnm2 about 9 yearsHaha, I do that too. No worries.
-
Chad Grant over 7 yearsThis is by no means thread safe, all you're doing is switching to the UI thread / Dispatcher. Not making the collection thread safe.
-
Philipp Munin over 7 yearsIsn't it better to put it on gist.github.com?
-
avenmore about 7 yearsNot thread safe, but it does answer the OP's question. Perhaps the question should be re-titled "How to safely add to a UI Thread's ObservableCollection from a different thread". Anyway, this is what I was looking for when I searched for "thread safe observablecollection".
-
Tobias about 7 years@Philipp Munin I put it on gist gist.github.com/anonymous/26d9d070619de58fa8e28ea21fff04fd
-
wilford almost 7 yearsThe collection works well, but it should really implement IDisposable since it has disposable fields like ReaderWriterLockSlim and ThreadLocal. In my scenario it ate up all available memory pretty quickly because it kept several large object graphs alive.
-
wonko realtime over 6 yearsThere might be a race condition; after many hours of running i always get a "An ItemsControl is inconsistent with its items source" ... "Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset)."
-
Robert Fraser over 6 yearsUh oh; thanks for pointing that out. I'll look over it, but for now probably best to use the Microsoft official one that's received a lot more testing.
-
Anton Krouglov almost 6 yearsObservableConcurrentCollection has somewhat limited functionality. Much better implementation is here codeproject.com/Articles/64936/…
-
shtse8 almost 6 yearsThis should not be marked as an answer because this is not a thread-safe solution. It just dispatch the event in the correct thread, but not safe.
-
Franck almost 6 years@shtse8 The question is not about thread safe collection. It is about accessing collection from another thread aka cross thread access. The dispatcher itself is thread safe but that make this kind of implementation thread friendly not thread safe.
-
Mark Gjøl over 5 yearsNote that this enables collection changes from other threads, but you still need to do synchronization if you are accessing the collection from multiple threads. Ie. the collection is not suddenly thread safe, but the binding in WPF is.
-
Martin over 4 years@wonkorealtime If I understand the situation and the code correctly, there actually is the race condition you mentioned: WPF expects that "a change to the collection and the notification of that change (through INotifyCollectionChanged) are atomic; no access from other threads can intervene" (docs.microsoft.com/en-us/dotnet/api/…) Example: Worker thread does an Add, queues the event, UI thread reads Count, receives the event and sees an inconsistency.
-
ToolmakerSteve about 4 yearsIf I understand the code here correctly.
BeginInvoke
is asynchronous. But the methods we are trying to "fix" (e.g.Clear
,Add
, ..) are synchronous. So a "mass replace" of allObservableCollections
is a dangerous "fix" that will almost certainly introduce a new kind of bug, where a thread does an Add, and then runs code that "assumes" the item has indeed been added. BROKEN: it won't be added until the invoked code runs, some time later. For example: if collection is empty, add an item with default values. Now usecollection[0]
=> exception (sometimes). -
ToolmakerSteve about 4 yearsTo add to @MarkGjøl's comment "accessing ... from multiple threads": you need to do
lock (YourLockObject){ .. }
around any code that might be called from some thread other than the UI thread. That is, even if you are "only calling from one thread", be clear that the UI thread is already involved, so if you are (or might be) on some other thread, then locking is needed. -
Franck about 4 years@ToolmakerSteve This can't happen, at the moment the dispatcher is called the item is already added. This event trigger after the collection has changed. It just synchronize the databind values with the interface.
-
ToolmakerSteve about 4 yearsAh, thanks - I misunderstood the sequence of events behind the scenes.