Как обновить ObservableCollection из BackgroundWorker с помощью MVVM? - PullRequest
5 голосов
/ 21 января 2011

, так как два дня я пытаюсь решить следующую проблему: У меня есть элемент управления WPF, где WrapPanel связан с ObservableCollection. Действие изменяет содержимое коллекции ObservableCollection. Содержимое загружается в BackgroundWorker. Сразу после действия, вызвавшего изменение содержимого, новый контент необходим в цикле foreach. Проблема в том, что загрузка контента идет медленно, поэтому для его подготовки нужно немного.

Моей первой попыткой было подождать фонового работника, пока для свойства IsBusy не будет установлено значение false. Но свойство IsBusy никогда не менялось во время ожидания! Вторая попытка состояла в том, чтобы попытаться манипулировать ObservableCollection непосредственно из BackgroundWorker. Конечно, безуспешно, потому что ObservableCollection находится в другом потоке, чем BackgroundWorker.

Я действительно очень много читал о том, как манипулировать контентом в многопоточном режиме. Но никто из них не работал. Пробные решения с помощью Dispatcher, "ThreadSafeObservableCollection", .....

Может кто-нибудь сказать мне, как я могу решить эту проблему? Есть ли простой способ редактировать содержимое потока пользовательского интерфейса в другом потоке? Или как правильно дождаться завершения работы BackgroundWorker?

EDIT: Но как я могу дождаться завершения работы BackgroundWorker ???

Ответы [ 4 ]

8 голосов
/ 21 января 2011

BackgroundWorker может помочь вам двумя способами.

Чтобы обновить коллекцию во время работы BGWorker, используйте событие ProgressChanged. Название этого события вводит в заблуждение - хотя вы можете обновить ход выполнения задачи, вы можете использовать его для всего, что нужно сделать в потоке пользовательского интерфейса (вызова), передавая объект с помощью Свойство UserState объекта ProgressChangedEventArgs.

У BGWorker также есть событие, когда оно заканчивается. Опять же, вы можете передать ему любую информацию, которую хотите, в свойстве Result объекта RunWorkerCompletedEventArgs в событии RunWorkerCompleted.

Следующий код взят из другого потока , на который я ответил о BackgroundWorker:

BackgroundWorker bgWorker = new BackgroundWorker();
ObservableCollection<int> mNumbers = new ObservableCollection<int>();

public Window1()
{
    InitializeComponent();
    bgWorker.DoWork += 
        new DoWorkEventHandler(bgWorker_DoWork);
    bgWorker.ProgressChanged += 
        new ProgressChangedEventHandler(bgWorker_ProgressChanged);
    bgWorker.RunWorkerCompleted += 
        new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
    bgWorker.WorkerReportsProgress = true;

    btnGenerateNumbers.Click += (s, e) => UpdateNumbers();

    this.DataContext = this;
}

void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    progress.Visibility = Visibility.Collapsed;
    lstItems.Opacity = 1d;
    btnGenerateNumbers.IsEnabled = true;
}

void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    List<int> numbers = (List<int>)e.UserState;
    foreach (int number in numbers)
    {
         mNumbers.Add(number);
    }

    progress.Value = e.ProgressPercentage;
}

void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
    Random rnd = new Random();
    List<int> numbers = new List<int>(10);

    for (int i = 1; i <= 100; i++)
    {
        // Add a random number
        numbers.Add(rnd.Next());            

        // Sleep from 1/8 of a second to 1 second
        Thread.Sleep(rnd.Next(125, 1000));

        // Every 10 iterations, report progress
        if ((i % 10) == 0)
        {
            bgWorker.ReportProgress(i, numbers.ToList<int>());
            numbers.Clear();
        }
    }
}

public ObservableCollection<int> NumberItems
{
    get { return mNumbers; }
}

private void UpdateNumbers()
{
    btnGenerateNumbers.IsEnabled = false;
    mNumbers.Clear();
    progress.Value = 0;
    progress.Visibility = Visibility.Visible;
    lstItems.Opacity = 0.5;

    bgWorker.RunWorkerAsync();
}
5 голосов
/ 21 января 2011

Нажатие ObservableCollection.Add на диспетчер потока пользовательского интерфейса должно работать.

App.Current.Dispatcher.Invoke(new Action(() => collection.Add(item)));
2 голосов
/ 21 января 2011

Вы можете обновить свою коллекцию в обработчике событий BackgroundWorker.RunWorkerCompleted. Он запускается в том же контексте синхронизации, в котором вы его запускали, и обычно является потоком пользовательского интерфейса, поэтому вы можете безопасно использовать любые связанные с ним материалы.

1 голос
/ 21 января 2011

BackGroundWorker запускает событие, когда оно закончено. То, что я делал в подобной ситуации:

У меня есть список, который не является наблюдаемой коллекцией

  • Установите включенный = false в моем окне и отобразите счетчик
  • Запуск фонового рабочего
  • в DoWork я заполняю список
  • в событии RunWorkerCompleted я копирую содержимое списка в свою наблюдаемую коллекцию, включаю вещи и прячу спиннер

так что все взаимодействие с коллекцией происходит в одном потоке - копирование обычно не является дорогой частью.

...