以并行方式将项添加到ListBox

时间:2015-06-11 12:12:09

标签: c# wpf listbox task-parallel-library backgroundworker

我正在编写一个简单的应用程序(用于测试目的),它将10 M个元素添加到 ListBox 。我使用 BackgroundWorker 进行工作,使用 ProgressBar 控件来显示进度。

My application

每个元素都只是一个" Hello World!"我在进程中添加的字符串和索引。我的程序需要大约7-8秒来填充 ListBox ,我想如果可以通过使用我PC上的所有可用内核(8)来加快这个速度。

为了达到这个目的,我尝试使用TPL库,更精确的是 Parallel.For 循环,但结果是不可预测的,或者它并不像我想要的那样工作它来。

这是我的申请代码:

    private BackgroundWorker worker = new BackgroundWorker();
    private Stopwatch sw = new Stopwatch();
    private List<String> numbersList = new List<String>();

    public MainWindow()
    {
        InitializeComponent();

        worker.WorkerReportsProgress = true;
        worker.DoWork += worker_DoWork;
        worker.ProgressChanged += worker_ProgressChanged;
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
    }

    private void btnAdd_Click(object sender, RoutedEventArgs e)
    {
        worker.RunWorkerAsync();
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        sw.Start();

        int max = 10000000;
        int oldProgress = 0;

        for (int i = 1; i <= max; i++)
        {
            numbersList.Add("Hello World! [" + i + "]");

            int progressPercentage = Convert.ToInt32((double)i / max * 100);

            // Only report progress when it changes
            if (progressPercentage != oldProgress)
            {
                worker.ReportProgress(progressPercentage);
                oldProgress = progressPercentage;
            }
        }
    }

    private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        pb.Value = e.ProgressPercentage;
    }

    private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        lstLoremIpsum.ItemsSource = numbersList;
        lblCompleted.Content = "OK";
        lblCompleted.Content += " (" + numbersList.Count + " elements added" + ")";
        lblElementiLista.Content += " (" +sw.Elapsed.TotalSeconds + ")";

        worker.Dispose();
    }
}

我尝试编写的并行实现(这在DoWork中):

        Parallel.For(1, max, i =>
        {
            lock (lockObject)
            {
                numbersList.Add("Hello World! [" + i + "]");
            }

            int progressPercentage = Convert.ToInt32((double)i / max * 100);

            // Only report progress when it changes
            if (progressPercentage != oldProgress)
            {
                worker.ReportProgress(progressPercentage);
                oldProgress = progressPercentage;
            }
        });

结果是应用程序冻结,并且需要 15秒来填充我的ListBox。 (元素也是无序的)

在这种情况下可以做些什么,而且并行性会加速&#34;填充&#34;过程

3 个答案:

答案 0 :(得分:3)

线程中的lock语句基本上减少了对顺序处理的并行处理,但却带来了获取锁定的开销(使其有效地变慢)。

此外,可以使用有限数量的线程池线程,因此您不会同时添加完整的10米线程。

我认为更好的方法是使用非UI线程填充列表,然后将其绑定 - 这将确保在1000万次迭代循环运行时UI不会被冻结/不可用:

public MainWindow()
{
    InitializeComponent();
    Task.Factory.StartNew(PopList);
}

然后你可以在需要时调用UI线程:

private void PopList()
{
    sw.Start();

    int max = 10000000;
    int oldProgress = 0;

    for (int i = 1; i <= max; i++)
    {
        numbersList.Add("Hello World! [" + i + "]");

        int progressPercentage = Convert.ToInt32((double)i / max * 100);

        // Only report progress when it changes
        if (progressPercentage != oldProgress)
        {
            Dispatcher.BeginInvoke(new Action(() => { pb.Value = progressPercentage; }));                    
            oldProgress = progressPercentage;
        }
    }

    Dispatcher.BeginInvoke(new Action(() => { lstLoremIpsum.ItemsSource = numbersList; }));
}

在MVVM世界中,您只需设置绑定的IEnumerable而不是ItemsSource,如上例所示。

答案 1 :(得分:0)

您正在锁定每个添加的列表,并且所有进程负载就是这样,向列表中添加一个元素,因此不会加快速度,因为实际上没有并行工作。

如果您的项目列表具有已知大小(看起来如此),则代替列表创建具有适当大小的数组,然后在并行for循环中将相应项目设置为它的值,这样就不会执行锁定,而且应该更快。

此外,在您的代码中,您不会显示填充列表视图的时间,只显示列表,因此我假设您使用此列表作为数据源,然后在设置它之前执行listView.BeginUpdate()并在设置之后它listView.EndUpdate(),它可能会加速一些事情,在添加元素时列表视图有点慢。

答案 2 :(得分:0)

如果您使用Parallel.For,那么您不需要BackgroundWorker。而工作人员无论如何都不会按预期工作,因为你正试图从另一个线程访问它。

删除BackgroundWorker并直接执行Parallel.For,使用Interlocked方法更新进度条:

private int ProgressPercentage { get; set; }

private void DoWork()
{
    Parallel.For(1, max, i =>
    {
        lock (lockObject)
        {
            numbersList.Add("Hello World! [" + i + "]");
        }

        int progressPercentage = Convert.ToInt32((double)i / max * 100);

        // Only report progress when it changes
        if (progressPercentage != oldProgress)
        {
            Interlocked.Exchange(ProgressPercentage, progressPercentage);
            ShowProgress();
        }
    });
}

private void ShowProgress()
{
    pb.Value = ProgressPercentage;
}