Обновление коллекции ObservableCollection с помощью фонового рабочего в MVVM


Хорошо, я недавно реализовал фоновый рабочий для выполнения сохранения и загрузки данных.

Однако заставить это работать с командой save оказалось трудно.

В принципе, моя команда save генерирует событие, которое уведомляет модель представления коллекции о том, что элемент был добавлен и что элемент должен быть добавлен в свою собственную коллекцию ObservableCollection.

В этот момент я получаю обычное исключение, говорящее, что я не могу обновить ICollection в другом потоке. Я уже пробовал создание нового типа списка, который вызывает Dispatcher.Invoke, однако это все равно создает то же исключение.

Мне было интересно, есть ли у кого-нибудь еще предложения о том, как лучше всего справиться с этим?

Итак, в настоящее время у меня есть класс, который наследует от ObservableCollection:

public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
    public ThreadSafeObservableCollection(List<T> 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);
        }
    }

Затем у меня есть класс модели представления, у которого есть фоновый работник, который выполняет сохранение.

После завершения сохранения событие запускается в другую модель представления для обновления ее списка.

    protected override void OnObjectAddedToRepository(object sender, ObjectEventArgs<cdAdministrators> 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);
    }

Оба списки создаются отдельным фоновым рабочим потоком.

3 10

3 ответа:

Если у вас есть код, который добавляет элемент в наблюдаемую коллекцию (предположительно в модели представления), оберните этот вызов Add в Вызов Dispatcher.BeginInvoke.

По общему признанию, это означает, что модель представления должна знать о диспетчере, который затем становится неудобным для тестирования... к счастью, не так уж сложно ввести свой собственный интерфейс IDispatcher и использовать инъекцию зависимостей обычным способом.

Как насчет этого?

public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
    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);
    }
}

Я нашел сообщение в блоге , которое использует диспетчер для управления всеми методами ObeservableCollection. Вот фрагмент кода, смотрите пост для всего класса.

public class DispatchingObservableCollection<T> : ObservableCollection<T>
{
    /// <summary>
    /// The default constructor of the ObservableCollection
    /// </summary>
    public DispatchingObservableCollection()
    {
        //Assign the current Dispatcher (owner of the collection)
        _currentDispatcher = Dispatcher.CurrentDispatcher;
    }

    private readonly Dispatcher _currentDispatcher;

    /// <summary>
    /// Executes this action in the right thread
    /// </summary>
    ///<param name="action">The action which should be executed</param>
    private void DoDispatchedAction(Action action)
    {
        if (_currentDispatcher.CheckAccess())
            action();
        else
            _currentDispatcher.Invoke(DispatcherPriority.DataBind, action);
    }

    /// <summary>
    /// Clears all items
    /// </summary>
    protected override void ClearItems()
    {
        DoDispatchedAction(() => base.ClearItems());
    }

    /// <summary>
    /// Inserts a item at the specified index
    /// </summary>
    ///<param name="index">The index where the item should be inserted</param>
    ///<param name="item">The item which should be inserted</param>
    protected override void InsertItem(int index, T item)
    {
        DoDispatchedAction(() => base.InsertItem(index, item));
    }