如何使用MVVM从BackgroundWorker内部更新ObservableCollection?

时间:2011-01-21 13:43:17

标签: wpf multithreading mvvm backgroundworker dispatcher

因为两天我试图解决以下问题: 我有一个WPF控件,其中WrapPanel绑定到ObservableCollection。操作会更改ObservableCollection的内容。内容将加载到BackgroundWorker中。在导致内容更改的操作之后,在foreach循环中需要新内容。问题是内容的加载速度很慢,所以需要一点准备。

我的第一次尝试是等待后台工作,直到IsBusy属性设置为false。但IsBusy的财产在等待期间从未改变过! 第二次尝试是尝试直接从BackgroundWorker操作ObservableCollection。当然没有成功,因为ObservableCollection在另一个线程中而不是BackgroundWorker。

我真的非常了解如何在跨线程范围内操作内容。但它们都没有奏效。使用Dispatcher,“ThreadSafeObservableCollection”,.....

尝试了解决方案

有谁能告诉我如何解决这个问题? 是否有一种简单的方法来编辑另一个线程中的UI线程的内容? 或者我如何正确等待BackgroundWorker完成?

修改 但是我怎么能等待BackgroundWorker完成呢?

4 个答案:

答案 0 :(得分:8)

BackgroundWorker可以通过两种方式为您提供帮助。

要在BGWorker运行时更新集合,请使用ProgressChanged事件。此事件的名称具有误导性 - 当您可以更新任务的进度时,您可以使用它实际使用它来通过传递对象来在UI(调用)线程中完成任何操作ProgressChangedEventArgs的UserState属性。

BGWorker完成后也会有一个事件。同样,您可以在RunWorkerCompleted事件中的RunWorkerCompletedEventArgs的Result属性中将任何信息传回给它。

以下代码来自another thread,我回答了有关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();
}

答案 1 :(得分:5)

ObservableCollection.Add推送到UI线程的调度程序应该有效。

App.Current.Dispatcher.Invoke(new Action(() => collection.Add(item)));

答案 2 :(得分:2)

您可以在BackgroundWorker.RunWorkerCompleted事件处理程序中更新您的集合。它运行在你启动它的同一个同步上下文中,这通常是一个UI线程,因此你可以安全地使用任何与UI相关的东西。

答案 3 :(得分:1)

BackGroundWorker在事件结束时触发事件。 我在类似情况下一直在做的是:

我有一个不是observablecollecion的列表

  • 在我的窗口中设置enabled = false并显示微调器
  • 启动后台工作者
  • 在DoWork中填写列表
  • 在RunWorkerCompleted事件中,我将列表内容复制到我的observablecollection并启用内容并隐藏微调器

所以与集合的所有交互都在同一个线程上 - 复制通常不是昂贵的部分。