Mettre à jour une ObservableCollection avec un travailleur d’arrière-plan dans MVVM

Ok, j’ai récemment mis en place un travailleur d’arrière-plan pour sauvegarder et charger les données.

Cependant, faire en sorte que cela fonctionne avec une commande de sauvegarde s’est avéré difficile.

Fondamentalement, ma commande de sauvegarde génère un événement, qui informe un modèle de vue de collection, qu’un élément a été ajouté et que l’élément doit être ajouté à son propre ObservableCollection.

À ce stade, l’exception habituelle me dit que je ne peux PAS mettre à jour une ICollection sur un autre thread. J’ai essayé de créer un nouveau type de liste qui appelle Dispatcher.Invoke , mais cela génère toujours la même exception.

Je me demandais si quelqu’un d’autre avait des suggestions sur la meilleure façon de s’y attaquer?

Donc, actuellement, j’ai une classe qui hérite de ObservableCollection:

 public class ThreadSafeObservableCollection : ObservableCollection { public ThreadSafeObservableCollection(List collection) : base(collection) { dispatcher = Dispatcher.CurrentDispatcher; rwLock = new ReaderWriterLock(); } protected override void InsertItem(int index, T item) { if (dispatcher.CheckAccess()) { if (index > this.Count) return; LockCookie c = rwLock.UpgradeToWriterLock(-1); base.InsertItem(index, item); rwLock.DowngradeFromWriterLock(ref c); } else { object[] obj = new object[] { index, item }; dispatcher.Invoke( DispatcherPriority.Send, (SendOrPostCallback)delegate { InsertItemImpl(obj); }, obj); } } 

J’ai ensuite une classe de modèle de vue qui a un travailleur d’arrière-plan qui effectue la sauvegarde.

Une fois la sauvegarde terminée, un événement est déclenché vers un autre modèle de vue pour mettre à jour sa liste.

  protected override void OnObjectAddedToRepository(object sender, ObjectEventArgs e) { Dispatcher x = Dispatcher.CurrentDispatcher; var viewModel = new AdministratorViewModel(e.EventObject, DataAccess); viewModel.RecentlyAdded = true; viewModel.ItemSelected += this.OnItemSelected; this.AllViewModels.Add(viewModel); RecentlyAddedViewModel = viewModel; OnPropertyChanged(null); } 

Les deux listes sont créées par un thread de travail d’arrière-plan distinct.

Si vous avez un code qui ajoute l’élément à la collection observable (vraisemblablement dans le modèle d’affichage), encapsulez cet appel dans un appel Dispatcher.BeginInvoke .

Certes, cela signifie que le modèle de vue doit connaître le répartiteur, qui devient alors difficile à tester … heureusement, il n’est pas trop difficile d’introduire votre propre interface IDispatcher et d’utiliser l’dependency injection de manière normale.

Que dis-tu de ça?

 public class ThreadSafeObservableCollection : ObservableCollection { private SynchronizationContext SynchronizationContext; public ThreadSafeObservableCollection() { SynchronizationContext = SynchronizationContext.Current; // current synchronization context will be null if we're not in UI Thread if (SynchronizationContext == null) throw new InvalidOperationException("This collection must be instantiated from UI Thread, if not, you have to pass SynchronizationContext to con structor."); } public ThreadSafeObservableCollection(SynchronizationContext synchronizationContext) { if (synchronizationContext == null) throw new ArgumentNullException("synchronizationContext"); this.SynchronizationContext = synchronizationContext; } protected override void ClearItems() { this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.ClearItems()), null); } protected override void InsertItem(int index, T item) { this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.InsertItem(index, item)), null); } protected override void RemoveItem(int index) { this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.RemoveItem(index)), null); } protected override void SetItem(int index, T item) { this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.SetItem(index, item)), null); } protected override void MoveItem(int oldIndex, int newIndex) { this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.MoveItem(oldIndex, newIndex)), null); } } 

J’ai trouvé un article de blog qui utilise Dispatcher pour gérer toutes les méthodes de ObeservableCollection. Voici un extrait du code, voir le post pour toute la classe.

 public class DispatchingObservableCollection : ObservableCollection { ///  /// The default constructor of the ObservableCollection ///  public DispatchingObservableCollection() { //Assign the current Dispatcher (owner of the collection) _currentDispatcher = Dispatcher.CurrentDispatcher; } private readonly Dispatcher _currentDispatcher; ///  /// Executes this action in the right thread ///  ///The action which should be executed private void DoDispatchedAction(Action action) { if (_currentDispatcher.CheckAccess()) action(); else _currentDispatcher.Invoke(DispatcherPriority.DataBind, action); } ///  /// Clears all items ///  protected override void ClearItems() { DoDispatchedAction(() => base.ClearItems()); } ///  /// Inserts a item at the specified index ///  ///The index where the item should be inserted ///The item which should be inserted protected override void InsertItem(int index, T item) { DoDispatchedAction(() => base.InsertItem(index, item)); }