2015-05-29 36 views
4

我開始嘗試向Windows窗體添加進度條,該窗體更新在Parallel.Foreach循環內運行的代碼的進度。爲了做到這一點,UI線程必須可用來更新進度條。我使用了一個Task來運行Parallel.Foreach循環以允許UI線程更新進度條。在.NET 4.0中使用Parallel.Foreach任務

在Parallel.Foreach循環內完成的工作相當密集。使用Task運行程序的可執行文件(不在Visual Studio中進行調試)後,程序無響應。如果我沒有任務運行我的程序,情況並非如此。我注意到兩個實例之間的主要區別在於,程序在沒有任務的情況下運行需要約80%的CPU,而在運行任務時約爲5%。

private void btnGenerate_Click(object sender, EventArgs e) 
    { 
     var list = GenerateList(); 
     int value = 0; 
     var progressLock = new object(); 

     progressBar1.Maximum = list.Count(); 

     Task t = new Task(() => Parallel.ForEach (list, item => 
     { 
       DoWork(); 
       lock (progressLock) 
       { 
        value += 1; 
       } 
     })); 

     t.Start(); 

     while (!t.IsCompleted) 
     { 
      progressBar1.Value = value; 
      Thread.Sleep (100); 
     } 
    } 

邊注:我知道

Interlocked.Increment(ref int___); 

工作到位的鎖。它被認爲更有效率嗎?

我的問題是三倍:

1)爲什麼會與工作程序無響應時的負荷要少得多?

2.)使用Task來運行Parallel.Foreach是否將Parallel.Foreach的線程池限制爲僅運行任務的線程?

3.)有沒有辦法讓UI線程響應,而不是在沒有使用取消標記的情況下睡1秒的時間?

我很感激任何幫助或想法,我花了相當多的時間研究這一點。如果我違反了任何發佈格式或規則,我也很抱歉。我試圖堅持他們,但可能錯過了一些東西。

+0

等待在UI線程上循環完成的任務就像在UI線程上執行它(稍微差一點)。看看'BackgroundWorker'或'async' – SimpleVar

+0

如果'DoWork()'訪問你的任何UI元素,它將導致死鎖發生。 –

+0

JohnathonSullinger - DoWork()不訪問任何UI元素。 @Yorye Nathan - 你能否詳細說明或舉個例子?今天之前我從來沒有用過多線程做過什麼。 –

回答

6

通過使用內置的Invoke方法在擁有Windows同步上下文時調用委託,可以極大地簡化代碼。

MSDN

在擁有此控件的基礎窗口句柄的線程上執行指定的委託。

Invoke方法搜索控件的父鏈,直到它找到一個控件或具有窗口句柄的窗體(如果當前控件的底層窗口句柄尚不存在)。

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 
    } 

    string[] GenerateList() => new string[500]; 
    void DoWork() 
    { 
     Thread.Sleep(50); 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     var list = GenerateList(); 
     progressBar1.Maximum = list.Length; 

     Task.Run(() => Parallel.ForEach(list, item => 
     { 
      DoWork(); 

      // Update the progress bar on the Synchronization Context that owns this Form. 
      this.Invoke(new Action(() => this.progressBar1.Value++)); 
     })); 
    } 
} 

的是,將屬於,從任務中這將調用相同的UI線程上Action委託。

我們嘗試回答您的問題

1)爲什麼會與工作程序無響應時的負荷要少得多?

我不是100%確定,但這可能與您在UI線程上鎖定成員有關。如果負載較小,那麼鎖定會更頻繁地發生,這可能會導致UI線程在進度條遞增時「掛起」。

您還正在運行一個while循環,該循環每100毫秒就會在UI線程中休眠。由於while循環,你會看到UI掛起。

2.)是否使用Task來運行Parallel.Foreach將Parallel.Foreach的線程池限制爲僅運行任務的線程?

它沒有。在Parallel.ForEach調用中將創建幾個任務。底層的ForEach使用partitioner來分散工作,而不是創建比必要的任務更多的任務。它會分批創建任務,並處理批次。

3.)有沒有辦法讓UI線程響應,而不是在不使用取消標記的情況下在.1秒內休眠?

我能夠通過移除while迴路,並使用Invoke方法先走一步,直接在UI線程上執行拉姆達來處理。

+0

這是.Net 4.5的一個很好的解決方案,但問題被標記爲.net-4.0。 OP可以使用['BeginInvoke()'](https://msdn.microsoft.com/en-us/library/system.windows.forms.control.begininvoke%28v=vs.110%29.aspx)來更新進度條? – dbc

+0

啊,我錯過了。是的他們可以。我會用一個例子更新我的答案。 –

+0

感謝您的迴應!不幸的是,我正在使用的項目是使用.NET 4.0,據我所知,它不支持Progress(T)類。 編輯:沒有看到有人已經發現了。 –