WPF:Parallel.Foreach中的进度条更新

时间:2016-12-07 19:14:31

标签: c# wpf parallel.foreach taskscheduler

我正在尝试在运行Parallel.Foreach时更新进度条,但在执行期间没有任何反应。仅当For循环结束时,Progressbar才会更新。如何使这段代码有效?

XAML

 <StackPanel>
        <Grid x:Name="LoadProgressGrid" Height="100"                  
                  Visibility="Visible">
            <ProgressBar x:Name="LoadProgress"
                Maximum="100"
                Minimum="1" />
            <TextBlock Margin="0,0,0,-5"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                FontSize="16"><Run Text="Import Progress"/></TextBlock>   
        </Grid>
        <Button  Height="35" Width="100" Margin="0,10" Content="Test" Click="Test_Click"/>
    </StackPanel>

C#

 private void Test_Click(object sender, RoutedEventArgs e)
        {
              decimal current=0;  
              List<string> lst = new List<string>();

              lst.Add("Foo");
              lst.Add("Foo");
              lst.Add("Foo");
              lst.Add("Foo");

              decimal max = 100000;

              var uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());

              Parallel.ForEach(lst,  (data) =>
              {
                for (int i = 0; i < max; i++)
                {
                  // Use the uiFactory above:
                  // Note the need for a temporary here to avoid closure issues!
                  current = current + 1;
                  uiFactory.StartNew( () => LoadProgress.Value = (double)(current/max)*100);
                }
              });


              MessageBox.Show("Done!");
         }   

2 个答案:

答案 0 :(得分:2)

正如评论中所指出的,Parallel.ForEach在循环完成之前不会返回,这将阻止它运行的线程,并且Servy在你的答案中的评论也表明你正在访问和更改状态没有同步或对象锁定的各种线程(参见Race Conditions)。

如果您不确定在UI线程上更改状态的正确方法,那么您可以让框架执行使用IProgress<T>捕获上下文的工作。

关于您的回答,您可以将async关键字直接放在Test_Click事件处理程序上,同时保留返回类型void,但请注意不建议或建议使用空格对于任何其他async方法,它们应返回TaskTask<T>read more here on why async void is a bad idea类型。

更重要的是,这里有一个使用async和非阻止代码报告进度并更新进度条的代码段,我已经评论了代码更具可读性。

// Add the async keyword to our event handler
private async void Button_Click(object sender, RoutedEventArgs e)
{
    //Begin our Task
    Task downloadTask = DownloadFile();

    // Await the task
    await downloadTask;
}

private async Task DownloadFile()
{
    // Capture the UI context to update our ProgressBar on the UI thread
    IProgress<int> progress = new Progress<int>(i => { LoadProgress.Value = i; });
    // Run our loop
    for (int i = 0; i < 100; i++)
    {
        int localClosure = i;
        // Simulate work
        await Task.Delay(1000);
        // Report our progress
        progress.Report((int)((double)localClosure / 100 * 100));
    }
}

答案 1 :(得分:0)

从这个回答:Using Task with Parallel.Foreach in .NET 4.0和Servy的评论我得到了它的工作。

 private  void Test_Click(object sender, RoutedEventArgs e)
        {
            test();

         }

        public async void test()
        {
            decimal current = 0;
            List<string> lst = new List<string>();

            lst.Add("Foo");
            lst.Add("Foo");
            lst.Add("Foo");
            lst.Add("Foo");

           decimal max = 10000;

          //Remove await (and async from signature) if, want to see the message box rightway.  
           await Task.Run(() => Parallel.ForEach(lst, (data) =>
            {
                for (int i = 0; i < max; i++)
                {                                        
                    current = current + 1;                    
                    Dispatcher.Invoke(new Action(() => LoadProgress.Value = (double)(current / max) * 100));
                }
            }));

            MessageBox.Show("Done!");
        }