2011-01-24 46 views
4

我想实现一个Parallel.ForEach循环替换旧foreach循环,但我无法更新我的UI(我有显示类似“X/Y文件处理”一个计数器)。我做了一个Parallel.For循环的例子来说明我的问题(标签没有被更新)。c#Parallel.For和UI更新?

using System; 
using System.Windows.Forms; 
using System.Threading.Tasks; 
using System.Threading; 

namespace FormThreadTest 
{ 
    public partial class Form1 : Form 
    { 
     private SynchronizationContext m_sync; 
     private System.Timers.Timer m_timer; 
     private int m_count; 

     public Form1() 
     {      
      InitializeComponent(); 

      m_sync = SynchronizationContext.Current; 

      m_count = 0; 

      m_timer = new System.Timers.Timer(); 
      m_timer.Interval = 1000; 
      m_timer.AutoReset = true; 
      m_timer.Elapsed += new System.Timers.ElapsedEventHandler(m_timer_Elapsed); 
      m_timer.Start(); 
     } 

     private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
     { 
      Task.Factory.StartNew(() => 
      { 
       m_sync.Post((o) => 
       { 
        label1.Text = m_count.ToString(); 
        Application.DoEvents(); 
       }, null); 
      }); 
     } 

     private void button1_Click(object sender, EventArgs e)   
     {  
      Task.Factory.StartNew(() => 
      { 
       Parallel.For(0, 25000000, delegate(int i) 
       { 
        m_count = i; 
       }); 
      }); 
     } 
    } 
} 

如果我改变按钮单击事件方法,并添加了Thread.Sleep(),它似乎给时间UI更新线程来完成其工作:

private void button1_Click(object sender, EventArgs e)   
     {  
      Task.Factory.StartNew(() => 
      { 
       Parallel.For(0, 25000000, delegate(int i) 
       { 
        m_count = i; 
        Thread.Sleep(10); 
       }); 
      }); 
     } 

是有没有办法避免睡眠,还是我需要在那里?这似乎是我的用户不会更新标签,除非我这样做?我觉得奇怪,因为我可以将应用程序窗口(它不锁) - 那么,为什么惯于标签更新了,我怎么可以改变我的代码,以更好地支持的Parallel.For(每个)和UI更新?

我一直寻找一个解决方案,但我不能似乎发现了什么(或者我可能寻找错误的东西?)。

问候 西蒙

+0

我对这个知之甚少,但是这可能与允许的线程数有关吗?如果Parallel.For将他们全部拿走并且不留下任何事件来抓取,那么这可以解释行为。但我对此并不确定,所以它真的只是一个疯狂的猜测,希望能激发一个知道更多的人。 :) – Chris 2011-01-24 14:36:52

+0

尝试在设置标签文本后放置Application.DoEvents()。这将让其他线程(特别是UI线程)有机会重画屏幕 – user2712361 2013-08-23 20:45:00

回答

2

我的猜测是,计数到25亿美元(并行!)花费不到一秒钟......所以你的计时器不会被解雇之前,计时结束。如果添加Thread.Sleep,整个事情将运行慢得多,这样就可以看到更新。

在另一方面,你的计时器事件处理程序看起来杂乱无章。你生成一个线程向你的用户界面发布消息,当你终于在你的用户界面线程上,你调用Application.DoEvents ...为什么?您应该能够删除任务创建和DoEvents调用。

编辑:我测试了您发布的程序,并看到标签更新两次。我的电脑需要超过一秒才能算到25米。我将这个数字增加到了10亿,并且标签更新了多次。

EDIT2:您可以将定时器处理减少

private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
    { 
     m_sync.Post((o) => 
     { 
      label1.Text = m_count.ToString(); 
     }, null); 
    } 

不幸的是,所显示的数字是不是当前处理的项目的数量,但该项目的是发生在当下要处理的索引定时器事件发生。你将不得不实施自我计数。这可以通过使用

Interlocked.Add(ref m_count, 1); 
+0

谢谢,这个作品:) – SimonDK 2011-01-25 11:02:53

1

并行尝试

//private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 

System.Threading.Tasks.Task.Factory.StartNew(() => 
{ 
    m_sync.Post((o) => 
    { 
    label1.Text = m_count.ToString(); 
    Application.DoEvents(); 
    }, null); 

    System.Threading.Thread.Sleep(1000;) 

}, System.Threading.Tasks.TaskCreationOptions.LongRunning); 

做,但它可能无法正常工作。你可能会得到线程异常,因为winform控件不会允许另一个不是创建线程的线程更新它。如果是这种情况,请尝试创建另一个方法并使用控件本身作为委托进行调用。

eg. label1.Invoke(updateUIHandler); 
4

我有一个类似的需求来更新我的图形用户界面,因为结果进来Parallel.ForEach()。不过,我采取了与你不同的方式。

public partial class ClassThatUsesParallelProcessing 
{ 
    public event ProcessingStatusEvent StatusEvent; 

    public ClassThatUsesParallelProcessing() 
    { } 

    private void doSomethingInParallel() 
    { 
     try 
     { 
      int counter = 0; 
      int total = listOfItems.Count; 

      Parallel.ForEach(listOfItems, (instanceFromList, state) => 
      { 
       // do your work here ... 

       // determine your progress and fire event back to anyone who cares ... 
       int count = Interlocked.Increment(ref counter); 

       int percentageComplete = (int)((float)count/(float)total * 100); 
       OnStatusEvent(new StatusEventArgs(State.UPDATE_PROGRESS, percentageComplete)); 
      } 
     } 
     catch (Exception ex) 
     { 

     } 
    } 
} 

您的GUI然后将有类似下面的内容:

private void ProcessingStatusEventHandler(object sender, StatusEventArgs e) 
{ 
    try 
    { 
     if (e.State.Value == State.UPDATE_PROGRESS) 
     { 
      this.BeginInvoke((ProcessHelperDelegate)delegate 
      { 
       this.progressBar.Value = e.PercentageComplete; 
      } 
     } 
    } 
    catch { } 
} 

我想在这里提出的是,你可以决定什么时候是有意义的,通过你确定你的进步的唯一一点循环。而且,由于这些循环迭代在后台线程中,您需要将GUI控制更新逻辑编组回到主(调度)线程。这只是一个简单的例子 - 只要确保你遵循这个概念,你就会好起来的。