How to make ObservableCollection thread-safe?
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
Fast performing and thread safe observable collection
ObservableCollection
can be fast, if it wants to. :-)
The code below is a very good example of a thread safe, faster observable collection and you can extend it further to your wish.
using System.Collections.Specialized;
public class FastObservableCollection<T> : ObservableCollection<T>
{
private readonly object locker = new object();
/// <summary>
/// This private variable holds the flag to
/// turn on and off the collection changed notification.
/// </summary>
private bool suspendCollectionChangeNotification;
/// <summary>
/// Initializes a new instance of the FastObservableCollection class.
/// </summary>
public FastObservableCollection()
: base()
{
this.suspendCollectionChangeNotification = false;
}
/// <summary>
/// This event is overriden CollectionChanged event of the observable collection.
/// </summary>
public override event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// This method adds the given generic list of items
/// as a range into current collection by casting them as type T.
/// It then notifies once after all items are added.
/// </summary>
/// <param name="items">The source collection.</param>
public void AddItems(IList<T> items)
{
lock(locker)
{
this.SuspendCollectionChangeNotification();
foreach (var i in items)
{
InsertItem(Count, i);
}
this.NotifyChanges();
}
}
/// <summary>
/// Raises collection change event.
/// </summary>
public void NotifyChanges()
{
this.ResumeCollectionChangeNotification();
var arg
= new NotifyCollectionChangedEventArgs
(NotifyCollectionChangedAction.Reset);
this.OnCollectionChanged(arg);
}
/// <summary>
/// This method removes the given generic list of items as a range
/// into current collection by casting them as type T.
/// It then notifies once after all items are removed.
/// </summary>
/// <param name="items">The source collection.</param>
public void RemoveItems(IList<T> items)
{
lock(locker)
{
this.SuspendCollectionChangeNotification();
foreach (var i in items)
{
Remove(i);
}
this.NotifyChanges();
}
}
/// <summary>
/// Resumes collection changed notification.
/// </summary>
public void ResumeCollectionChangeNotification()
{
this.suspendCollectionChangeNotification = false;
}
/// <summary>
/// Suspends collection changed notification.
/// </summary>
public void SuspendCollectionChangeNotification()
{
this.suspendCollectionChangeNotification = true;
}
/// <summary>
/// This collection changed event performs thread safe event raising.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
// Recommended is to avoid reentry
// in collection changed event while collection
// is getting changed on other thread.
using (BlockReentrancy())
{
if (!this.suspendCollectionChangeNotification)
{
NotifyCollectionChangedEventHandler eventHandler =
this.CollectionChanged;
if (eventHandler == null)
{
return;
}
// Walk thru invocation list.
Delegate[] delegates = eventHandler.GetInvocationList();
foreach
(NotifyCollectionChangedEventHandler handler in delegates)
{
// If the subscriber is a DispatcherObject and different thread.
DispatcherObject dispatcherObject
= handler.Target as DispatcherObject;
if (dispatcherObject != null
&& !dispatcherObject.CheckAccess())
{
// Invoke handler in the target dispatcher's thread...
// asynchronously for better responsiveness.
dispatcherObject.Dispatcher.BeginInvoke
(DispatcherPriority.DataBind, handler, this, e);
}
else
{
// Execute handler as is.
handler(this, e);
}
}
}
}
}
}
Also ICollectionView
that sits above the ObservableCollection
is actively aware of the changes and performs filtering, grouping, sorting relatively fast as compared to any other source list.
Again observable collections may not be a perfect answer for faster data updates but they do their job pretty well.
How to enable thread-safe access to a collection
Unfortunately, there is no such method that could synchronized access to a collection that is used on multiple threads within UWP platform. You can only update a data-bound ObservableCollection in the dispatcher thread.
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// Update ObservableCollection
});
Is .NET ObservableCollection ToList() thread safe? If not, how to proceed
The implementation of ToList
extension method boils down to copying items from one array to another via Array.Copy
method, which, while hiding Collection was modified
error from you is not thread-safe and you may face weird behavior when underlying items are changed during Array.Copy
calll.
What I'd suggest it to use CollectionView
for binding, I've been using it for quite a long time in similar cases and faced no issues so far.
// somewhere in .ctor or other init-code
var logsForDisplay = new CollectionView(this.Logs);
logsForDisplay.Predicate = log => ((Log)log).Date == DateTime.Now.Day;
public CollectionView LogsForDisplay { get { return this.logsForDisplay; } }
You can have another CollectionView
for different use case, e.g.:
// somewhere in .ctor or other init-code
var yesterdaysLogs = new CollectionView(this.Logs);
yesterdaysLogs.Predicate = log => ((Log)log).Date == DateTime.Now.AddDays(-1).Day;
public CollectionView YesterdaysLogs{ get { return this.yesterdaysLogs; } }
WPF ObservableCollection Thread Safety
I typically initialize the dispatcher used by my view models in a common view model base to help ensure it is the UI thread dispatcher, as Will mentions.
#region ViewModelBase()
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary>
protected ViewModelBase()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
#endregion
#region Dispatcher
/// <summary>
/// Gets the dispatcher used by this view model to execute actions on the thread it is associated with.
/// </summary>
/// <value>
/// The <see cref="System.Windows.Threading.Dispatcher"/> used by this view model to
/// execute actions on the thread it is associated with.
/// The default value is the <see cref="System.Windows.Threading.Dispatcher.CurrentDispatcher"/>.
/// </value>
protected Dispatcher Dispatcher
{
get
{
return _dispatcher;
}
}
private readonly Dispatcher _dispatcher;
#endregion
#region Execute(Action action)
/// <summary>
/// Executes the specified <paramref name="action"/> synchronously on the thread
/// the <see cref="ViewModelBase"/> is associated with.
/// </summary>
/// <param name="action">The <see cref="Action"/> to execute.</param>
protected void Execute(Action action)
{
if (this.Dispatcher.CheckAccess())
{
action.Invoke();
}
else
{
this.Dispatcher.Invoke(DispatcherPriority.DataBind, action);
}
}
#endregion
You could then invoke an action on the view model dispatcher like this:
this.Execute(
() =>
{
this.OnNewItems(newItems);
}
);
Related Topics
The State of Linkers for .Net Apps (Aka "Please Sir, May I Have a Linker" 2009 Edition)
How to Write Output from a Unit Test
Find() VS. Where().Firstordefault()
Modelstate.Isvalid Even When It Should Not Be
JSON Serialize Properties on Class Inheriting List
How to Check If a Property Exists on a Dynamic Anonymous Type in C#
Starting and Stopping Iis Express Programmatically
Dependency Injection - New Instance Required in Several of a Classes Methods
Log4Net, How to Add a Custom Field to My Logging
How to Extract Subscript/Superscript Properly from a PDF Using Itextsharp
Return Content with Ihttpactionresult for Non-Ok Response
C# Generic Type Inference with Multiple Types
Using Bindingoperations.Enablecollectionsynchronization
Wix Service Installer Overrides Service Installer Settings
The Application Called an Interface That Was Marshalled for a Different Thread - Windows Store App