Обновите коллекцию ObservableCollection с фоновым рабочим в MVVM - PullRequest
10 голосов
/ 02 сентября 2010

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

Однако заставить его работать с командой сохранения оказалось трудным.

По сути, моя команда сохранениягенерирует событие, которое уведомляет модель представления коллекции, что Item был добавлен и что элемент должен быть добавлен к его собственной 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 ]

7 голосов
/ 02 сентября 2010

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

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

3 голосов
/ 02 сентября 2010

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

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);
    }
}
2 голосов
/ 12 октября 2010

Я нашел сообщение в блоге , в котором Диспетчер управляет всеми методами 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));
    }
...