0

I've got a MVVM setup.

My model periodically calls some service and then invokes an action on the ViewModel which then updates some variables exposed to the View.

The variable is an ReadOnlyObservableCollection<T>, which has an ObservableCollection<T> it listens on.

The problem is that the Model calls the callback from a different thread, and thus it doesn't allow me to clear the ObservableCollection<T> on a different thread.

So I thought: use the dispatcher, if we aren't on the correct thread, invoke it:

    private void OnNewItems(IEnumerable<Slot> newItems)
    {
        if(!Dispatcher.CurrentDispatcher.CheckAccess())
        {
            Dispatcher.CurrentDispatcher.Invoke(new Action(() => this.OnNewItems(newItems)));
            return;
        }

        this._internalQueue.Clear();
        foreach (Slot newItem in newItems)
        {
            this._internalQueue.Add(newItem);
        }
    }

Code is pretty straightforward I think.

The problem is that, even though I execute it on the correct thread (I think) it still throws me an exception on the .Clear();

Why is this occuring? How can I work around it without creating my custom ObservableCollection<T>?

Anemoia
  • 7,928
  • 7
  • 46
  • 71
  • 'Dispatcher.CurrentDispatcher` is thread-centric. Check it from the UI thread, create an Object Id, then check it when you call CheckAccess. Betcha find its different. –  Aug 15 '11 at 16:24
  • 1
    .NET 4.5 adds support for this scenario; see http://stackoverflow.com/a/14602259/50079 – Jon Jan 30 '13 at 10:56
  • Try http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony Apr 15 '14 at 00:55

2 Answers2

2

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);
    }
);
Oppositional
  • 11,141
  • 6
  • 50
  • 63
0

A neat fix of this problem found on Codeproject- Multi-Threaded ObservableCollection and NotifyCollectionChanged Wrapper

JeeShen Lee
  • 3,476
  • 5
  • 39
  • 59
  • 1
    Actually that version wasn't thread-safe either; try http://www.codeproject.com/Articles/64936/Multithreaded-ObservableImmutableCollection – Anthony Apr 15 '14 at 00:55