2016-09-22 66 views
2

我能夠從代碼1更新UI而不是從2Task.Parallel更新UI。爲什麼這樣?

代碼1

Parallel.ForEach(names, name => 
    { 
    lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId; 
    }); 

代碼2

Task.Factory.StartNew(() => 
      { 
       Parallel.ForEach(names, name => 
       { 
        lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId; 
       }); 
      }); 

我知道代碼2不會更新用戶界面,因爲它的輔助線程。但爲什麼代碼1更新UI?不平行foreach運行不同的線程?如果是的話,爲什麼它更新UI?

的代碼1輸出

enter image description here

+1

Parallel.ForEach重用父線程,這就是它阻塞的原因。其中一個線程實際上是UI線程,所以在更新UI –

+0

時沒有問題,但如果它使用父線程,那麼爲什麼有兩個不同的線程ID? –

+1

此代碼在哪裏運行? –

回答

7

兩個代碼段工作...真的。

的問題是,第一碼塊的運行,而它的塊UI線程。第二個代碼塊啓動任務,然後繼續。

的問題是無法在使用多線程的,由於這兩個實施例使用多個線程來改變標籤的值。問題在於表單狀態。

我假設你運行表單構造的代碼。在第一種情況下,沒有創建句柄,所以的操作不需要UI線程。它只是更新後備值。在第二種情況下,在分割毫秒中需要創建任務,它會爲表單創建句柄。當需要更新標籤時,該操作需要UI線程

如果你把在該任務的Wait你會看到它也可以工作。如果您將代碼移動到OnHandleCreated,則兩個語句都會失敗。

+2

在第二個線程嘗試更新UI時,在「Click」事件中運行代碼會導致跨線程異常。你是怎麼嘗試的?你在哪裏運行代碼?它可能在構造函數或'Load'處理程序中嗎? –

+1

我從構造函數中運行它。如果你把代碼移到'OnHandleCreated',它會失敗。 –

+1

現在這是真正的答案。 – Evk

-1

Parallel.Foreach最終可能運行在不同的線程的任務。這裏有一篇關於並行循環如何工作的文章MSDN

根據你的代碼的位置,你仍然可以通過調用Invoke方法並傳遞一個委託來使用Task.Factory.StartNew方法。這將搜索控件的父鏈並找到具有窗口句柄的控件。

你裏面的任務代碼應該是這樣的:

this.Invoke(new Action(() => this.lblText.Text += "\n" + name + " Thread " + Thread.CurrentThread.ManagedThreadId)); 
+0

這與這個問題有什麼關係? OP並沒有問爲什麼Parallel並行實際工作。問題是爲什麼禁止UI更新成功 –

+0

我認爲問題是「不要求並行foreach運行不同的線程?如果是,那麼它爲什麼更新UI?」......當MSDN文章解釋如何和爲什麼並行foreach不總是運行在不同的線程中。 –

+0

問題不在於詢問'Parallel.ForEach'。它詢問意外的UI行爲 –

2

此代碼只能在窗體的構造函數中使用。此時該對象沒有UI或UI句柄,因此不需要更新UI。代碼只是改變標籤控件的屬性。

如果試圖在OnLoad方法或Click事件處理程序,你會得到預期的跨線程訪問異常相同的代碼。

的形式和他們的控制都不是真正的UI對象。應用程序發送消息到操作系統,告訴哪些Windows控件顯示在哪裏,如何修改它們等。這些消息由Windows句柄標識。

構造函數雖然在表單甚至被創建之前執行,所以沒有UI發送任何消息。更新標籤文本時,只需修改包含在UI初始化後將發送給操作系統的值的字符串即可。

創建句柄後,對Text屬性的任何修改都會向OS發送消息,而.NET明確阻止該消息。原因是從多個線程發送消息會導致無序傳遞到操作系統和一個亂七八糟的用戶界面。

+1

感謝您用簡單的語言解釋帕特里克的答案。 –