2011-01-24 47 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控制更新邏輯編組回到主(調度)線程。這只是一個簡單的例子 - 只要確保你遵循這個概念,你就會好起來的。