How to Make Observablecollection Thread-Safe

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



Leave a reply



Submit