从Parallel ForEach更新UI

时间:2015-08-04 17:05:08

标签: c# wpf parallel-processing

我目前正在开展一个侧面项目,我必须使用ManagementObject处理有关虚拟机的大量数据,以获取有关每台计算机的信息。

我希望能够处理我并行找到的每个虚拟机,在每次迭代完成后更新进度条(因此虚拟机已完成处理)。目前我总共有大约81个虚拟机。我在WPF中有一个简单的按钮可以解决这个问题:

        ClusterCrawler testCrawler = new ClusterCrawler("MyCluster");
        StartButton.IsEnabled = false;
        List<RTClusterNode> clustNodeParallel = null;
        clustNodeParallel = await Task.Run(()=>testCrawler.CrawlAndReturnNodesParrallelAsync(CrawlProgressBar));

其中CrawlProgressBar是主窗口中按钮所在的ProgressBar

我的CrawlAndReturnNodesParallelAsync类中存在ClusterCrawler方法并处理虚拟机列表,但最后它使用ParallelForEach循环来加速处理:

        public async Task<List<RTClusterNode>> CrawlAndReturnNodesParrallelAsync(ProgressBar pBar)
        {
            //Some processing done here

            int count = 0;
            Parallel.ForEach(clusterNodes_hr, node =>
            {
                foreach (var vm in node.VmList)
                {
                    Console.WriteLine("Crawling VM: " + vm.VmName);
                    VirtualMachineCrawler vmCrawler = new VirtualMachineCrawler(vm);
                    vmCrawler.CrawlForDataDiskSpace();
                    Interlocked.Increment(ref count);
                    Console.WriteLine("Current count: " + count);
                    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate(){
                        pBar.Value = count;
                    });
                }
            });
            return clusterNodes_hr;
        }

我觉得我在这里没有正确使用Dispatcher,因为即使我尝试仍然会得到异常,说线程无法访问此对象(进度条),因为它不拥有它。

关于如何更新进度条作为每个循环的并行执行的任何想法?

修改

@Servy对此有正确的答案。我还注意到代码中的一个问题,直到现在我还没有发现。在CrawlAndReturnNodesParallelAsync内更高,我有以下一行:

pBar.Maximum = totalVms;

之前我没有注意到这一点,因为当我将调试器连接到我正在运行代码的远程机器上时,调试器总是超时,然后我才有机会确切地看到代码失败的行。我已经在这里发布了关于这个问题但尚未深究其中的问题。

除此之外,由于时间的限制,我做了我以前的解决方案和@Servy的混合。我的Button代码现在包括:

IProgress<int> progress = new Progress<int>(n => CrawlProgressBar.Value = n);
clustNodeParallel = await Task.Run(()=>testCrawler.CrawlAndReturnNodesParrallelAsync(progress, CrawlProgressBar));

实际的方法签名更改为:

public List<RTClusterNode> CrawlAndReturnNodesParrallelAsync(IProgress<int> progress, ProgressBar pBar)

我在这里使用了@ Servy的解决方案:

Parallel.ForEach(clusterNodes_hr, node =>
{
    foreach (var vm in node.VmList)
    {
        Console.WriteLine("Crawling VM: " + vm.VmName);
        VirtualMachineCrawler vmCrawler = new VirtualMachineCrawler(vm);
        vmCrawler.CrawlForDataDiskSpace();
        Interlocked.Increment(ref count);
        Console.WriteLine("Current count: " + count);
        progress.Report(count);
    }
});

但是我也使用了我早期的解决方案,当我设置ProgressBar的最大值时,我最初几行来解决问题:

Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate()
{
    pBar.Maximum = totalVms;
});

1 个答案:

答案 0 :(得分:1)

使用Progress<T>类根据后台任务的进度更新UI。它将代表您处理所有UI编组,因此您不需要明确地执行任何操作。

public async Task<List<RTClusterNode>> CrawlAndReturnNodesParrallelAsync(ProgressBar pBar)
{
    IProgress<int> progress = new Progress<int>(n => pBar.Value = n);
    //Some processing done here

    int count = 0;
    Parallel.ForEach(clusterNodes_hr, node =>
    {
        foreach (var vm in node.VmList)
        {
            Console.WriteLine("Crawling VM: " + vm.VmName);
            VirtualMachineCrawler vmCrawler = new VirtualMachineCrawler(vm);
            vmCrawler.CrawlForDataDiskSpace();
            Interlocked.Increment(ref count);
            Console.WriteLine("Current count: " + count);
            progress.Report(count);
        }
    });
    return clusterNodes_hr;
}