Какой лучший способ обновить ObservableCollection из другого потока?

31

Я использую BackgroundWorker для обновления ObservableCollection, но он дает эту ошибку:

"Этот тип CollectionView делает не поддерживают изменения в своих SourceCollection из потока отличной от потока Диспетчер."

Какой лучший и самый элегантный способ решить эту проблему, с наименьшим количеством работы. Я не хочу писать низкоуровневый многопоточный код на основе блокировки.

Я видел некоторые решения в Интернете, но им уже несколько лет, поэтому не уверен, какой последний консенсус касается решения этой проблемы.

  • 1
    проверить это: stackoverflow.com/questions/528999/…
  • 1
    Точная копия: stackoverflow.com/questions/187069/… . Ответ Марка Ингрэма, похоже, на то, что вы ищете.
Показать ещё 4 комментария
Теги:
multithreading
observablecollection
parallel-processing

5 ответов

12
Лучший ответ

Если MVVM

public class MainWindowViewModel : ViewModel {

    private ICommand loadcommand;
    public ICommand LoadCommand { get { return loadcommand ?? (loadcommand = new RelayCommand(param => Load())); } }

    private ObservableCollection<ViewModel> items;
    public ObservableCollection<ViewModel> Items {
        get {
            if (items == null) {
                items = new ObservableCollection<ViewModel>();
            }
            return items;
        }
    }

    public void Load() {
        BackgroundWorker bgworker = new BackgroundWorker();
        bgworker.WorkerReportsProgress = true;
        bgworker.DoWork += (s, e) => {
            for(int i=0; i<10; i++) {
                System.Threading.Thread.Sleep(1000);
                bgworker.ReportProgress(i, new List<ViewModel>());
            }
            e.Result = null;
        };
        bgworker.ProgressChanged += (s, e) => {
            List<ViewModel> partialresult = (List<ViewModel>)e.UserState;
            partialresult.ForEach(i => {
                Items.Add(i);
            });
        };
        bgworker.RunWorkerCompleted += (s, e) => {
            //do anything here
        };
        bgworker.RunWorkerAsync();
    }
}
  • 0
    Спасибо, но RunWorkerCompleted будет работать после выхода из BGW? Потому что я хочу, чтобы интерфейс обновлялся на каждой итерации, чтобы я мог сразу увидеть все изменения.
  • 0
    Вы можете использовать RunWorkerCompleted для частичного обновления, позвольте мне обновить код
Показать ещё 7 комментариев
37

Если вы инициализируете коллекцию в конструкторе, она будет включена в поток приложений по умолчанию.

Чтобы вызвать основной поток, вы можете сделать это:

Application.Current.Dispatcher.Invoke((Action)(() =>
    {
       //Do something here.
    }));

Вы должны отправить анонимного делегата в качестве действия, иначе он запутается ¯\O_o/¯

Если вы используете Async CTP, вы можете сделать это

Application.Current.Dispatcher.InvokeAsync(()=>
    {
       //Do something here.
    });
  • 0
    Спасибо! работает для меня :)
4

Вы используете BGW, он был разработан для решения вашей проблемы. Но вы должны будете использовать его правильно, обновите коллекцию в обработчике событий ProgressChanged или RunWorkerCompleted. Если это то, что вы делаете, вы создали экземпляр BGW в неправильном потоке. Это должно быть сделано в потоке пользовательского интерфейса.

  • 0
    Спасибо, Ганс. Как я узнал, что создал его в потоке пользовательского интерфейса? Я в основном создал BGW внутри моего объекта VM. Также я не обновляю ObservableCollection, но ObservableCollection внутри каждого элемента внутри основной ObservableCollection. Поскольку у каждого элемента есть список значений, которые они содержат, я хочу обновить их. Я могу обновить другие свойства каждого элемента внутри BGW, но не вызывать Add для этой вложенной ObservableCollection.
  • 0
    Я не знаю, вы говорите о деталях кода, который я не вижу. Неожиданный факт заключается в том, что вы не можете обновить все, что обновляете в DoWork. Используйте один из обработчиков событий. При необходимости используйте вспомогательную коллекцию для хранения промежуточных результатов.
Показать ещё 1 комментарий
3

Попробуйте следующее:

this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
() =>
{

 //Code

}));
1

У меня была такая же проблема при перезагрузке наблюдаемого набора из события (в DataReceived), поднятого классом последовательного порта. Я использовал MVVM; Я попытался обновить коллекцию BackgroundWorker, но он поднял "Этот тип CollectionView не поддерживает изменения в SourceCollection из потока, отличного от потока Dispatcher". Я попробовал много других решений, найденных в Интернете, но единственный, кто решил мою проблему (сразу!), Состоял в использовании многопоточной наблюдаемой коллекции (я использовал ту, что была в этом сообщении: Где Я получаю потокобезопасный CollectionView?)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню