【问题标题】:WPF : Progress Bar update in Parallel.ForeachWPF:Parallel.Foreach 中的进度条更新
【发布时间】:2016-12-08 07:26:32
【问题描述】:

我试图在运行Parallel.Foreach 时更新进度条,但在执行过程中没有任何反应。进度条仅在 For 循环结束时更新。我怎样才能使这段代码工作?

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!");
         }   

【问题讨论】:

  • 您确定 LoadProgress.Value 是小数而不是整数?
  • 如果您希望 UI 真正能够做事,请不要阻塞 UI 线程。
  • 你不是为了不使用闭包而创建一个临时的,实际上并不涉及你创建一个临时的;你只是继续使用闭包
  • Parallel.ForEach 是一个阻塞动作,直到循环结束才会返回。

标签: c# wpf parallel.foreach taskscheduler


【解决方案1】:

正如 cmets 中所指出的,Parallel.ForEach 在循环完成之前不会返回,这将阻塞它运行的线程,Servy 在您的回答中的 cmets 还说您正在访问和更改来自各个线程的状态没有同步或对象锁定(参见Race Conditions)。

如果您不确定在 UI 线程上更改状态的正确方法,那么您可以让框架使用IProgress&lt;T&gt; 来完成捕获上下文的工作。

关于您的回答,您可以将async 关键字直接放在Test_Click 事件处理程序上,同时保持返回类型void,但请记住,不建议或建议任何其他async 使用void方法,它们应该返回 TaskTask&lt;T&gt;read more here on why async void is a bad idea 的类型。

更重要的是,这是一个使用async 和非阻塞代码来报告进度和更新进度条的sn-p,我已将代码注释为更具可读性。

// 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));
    }
}

【讨论】:

    【解决方案2】:

    从这个答案: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!");
            }
    

    【讨论】:

    • 为什么投反对票?是不是有什么不对,或者我做错了什么?请告诉我。
    • 所以你正在调度一个线程池线程来调度一个线程池线程来运行一堆操作来调度 UI(你从一开始就在那里)线程来设置一个值。这只是糟糕的设计。更不用说在不同步的庄园中从各个线程池线程中对状态的无效访问,这是完全不安全的。
    • 好的。感谢您的回复。我对 C# 很陌生,并且是第一次尝试任务。您能否建议解决方案。是否可以从 Parallel.ForEach 更新 WPF 进度条?
    • 您的代码首先没有做任何有意义的事情。提供一个合理的代码实现,将进度条设置为一个随机值,然后继续,一开始并不是一件明智的事情。
    • 当然。将尝试发布更明智的示例。再次感谢。
    猜你喜欢
    • 2013-03-08
    • 2020-11-05
    • 2015-01-24
    • 2014-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-10
    相关资源
    最近更新 更多