2010-01-18 76 views
5

BackgroundWorker在C#線程安全嗎?如何確保用戶界面響應使用BackgroundWorker

我之所以問這個是因爲我得到一個線程創建的

控件不能 的父對象的控制上 不同的線程

例外吧。這是我的DoWork事件代碼:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
{ 



    var openFile = document.Open(MyFileName); 
    e.Result = openFile; 
} 

其中document是在創建父窗體時被初始化的UI控件。在Open方法期間,document中的各種屬性將被填充。

我試圖改變代碼來調用,但同樣的問題仍然存在。即,

document.GetType().GetMethod("Open)".Invoke(document, new object[]{MyFileName}) 

將產生相同的誤差作爲上方。

任何想法如何操縱document控件?換句話說,如何讓上面的代碼工作?

編輯:有人建議我使用Control.Invoke,但它仍然沒有工作(兩個線程掛起)。這是我試過的代碼:

private delegate bool OpenFile(string filePath); 
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
{ 



    OpenFile oF = new OpenFile(document.Open); 
    var openFile = Invoke(oF, MyFileName); // it doesn't really matter whether I use BeginInvoke or Invoke, or other Control.Invoke, the end result is the same. Both the main thread hosting the document and the thread that launches the UI hanged. 

    e.Result = openFile; 
} 

回答

1

雖然這是我不清楚你究竟由BackgroundWorker的線程安全的意思是,問題不在於對象; Windows窗體控件被設計爲在單個線程(UI線程)上進行操作。您不應該在不同的線程上操作Windows窗體對象。您可以通過使用Control.Invoke方法調用從其他線程UI線程操作(您目前使用的是由反射提供與Invoke方法是完全無關的這個問題):

Invoke(new Action(MethodToRunInUIThread)); 

void MethodToRunInUIThread() { 
    // do stuff here. 
} 

順便說一句,這不是如果你所做的只是操縱UI對象,那麼使用後臺工作者是沒有意義的。

+0

@Mehdrad,我需要一個後臺工作來保持UI的響應,這是*爲什麼它是有道理的。 – Graviton

+0

@Ngu Soon Hui:當然,如果你有一個長期運行的任務,不只是UI操作,你會使用它。如果你在DoWork方法中所做的所有操作都是操作UI對象,那麼你不應該使用BackgroundWorker,原因是你最終必須在UI線程上執行所有這些UI操作。 –

+0

它可以是有道理的,它可能更微妙。線程可能只是通知「UI線程」「發生了什麼新事情」,然後它取決於UI需要更新UI的任何通知處理程序。 –

0

BackgroundWorker是一個基於線程的結構。線程安全問題關於執行同時任務時的功能。也許你所要求的是關於winforms控件,它們是通過用戶界面線程的唯一線程訪問的。

2

您可以像Mehrdad Afshari建議的那樣調用,也可以使用UI線程中返回的bgw進度事件。或者在UI線程中返回的工作完成事件。兩者之間的區別是WorkCompleted在最後僅被觸發一次。你從DoWork中解僱你的進步。

+0

「如何利用UI線程中返回的bgw進度事件」解決我的問題,即能夠具有響應式UI? – Graviton

+2

也許這是對BGW爲你做的一個誤解。如果你對UI控件進行了大量的操作,並且因此速度很慢,並且導致你的UI拉白或者凍結,bgw不能幫你!但是,如果您正在做其他事情(除了觸摸ui控件),例如轉到慢速web服務或數據庫,那麼在bgw上執行此操作將允許您的ui保持響應。例如,您可以執行密集操作x,然後使用進度事件向您的用戶界面報告操作x已完成,這裏是中間結果,下一步操作 –

5

這不是線程問題,而是它試圖調用UI控件上的方法。在WPF和WinForms控件中,只能在UI線程(通常有一個)上調用。你不會說你正在使用哪一個,但你需要調用WinForms的Control.Invoke方法或WPF的Dispatcher.Invoke

您顯示的Invoke()反射方法實際上會調用當前線程上的方法。

+0

我在我的DoWork事件中嘗試了這段代碼,並且它仍然不起作用:'this.Invoke(document.Open)' – Graviton

+0

你仍然在調用錯誤的方法。如果'document'是一個Windows窗體控件,那麼你可以調用'document.Invoke(MyMethod)',其中'MyMethod'是一個調用'document.Open'的委託。根據簽名,您可以直接調用「document.Invoke(document.Open)」。 – GraemeF

+0

@GraemeF,如果'document.Invoke'不可用? – Graviton

1

如果UI控件的這種功能需要很長時間才能執行,那麼可能沒有太多可以做的事情。當在UI線程上發生長時間運行的操作時發生「凍結」,並且如果該控件的該功能不是專門用於線程安全的,則必須在主線程上運行。

通常情況下,您希望將「文檔」功能從顯示它的控件中分離出來。這樣,您的文檔就可以加載到單獨的獨立線程上,並在稍後準備就緒時顯示。否則,控件本身必須實現多線程加載例程以減緩加載凍結。

既然你指定這是你評論的第三方控件,你可能是出於運氣在這裏。

0

@Graviton,與回答的相關任務是找到here。此人使用BackgroundWorker更新文本框,同樣的概念適用(您的只是一個工作線程)。

+0

是的,確實如此,看我更新的問題。我發現在應用鏈接中指定的技術之後,這兩個線程都被掛起。 – Graviton

+0

看起來您沒有將InvokeRequired檢查放在適當的位置,並且按照示例中的顯示調用BeginInvoke()調用。 InvokeRequired確保您處於正確的線程中,並且使用BeginInvoke()調用基本上會調用回當前的方法。我知道文檔不提供Invoke方法,因此請使用表單對象(這可能是您在第二個代碼示例中調用Invoke()時所做的操作)。請記住,調用BeginInvoke()是一回事...方法需要繼續調用BeginInvoke(),直到InvokeRequired爲false。 –

0

您需要在DoWork中使用Control.BeginInvoke()。這將異步執行委託,因此將確保調用線程不會「掛起」。

Control.Invoke()將執行其他線程也委託,但會導致調用線程等待它完成。

通常在Windows窗體中,最好使用Control.BeginInvoke()儘可能幫助避免在一個線程等待另一個線程時可能發生的線程之間發生死鎖,如同Control.Invoke()一樣。

如果「文件」的對象從Control繼承,你可以簡單地調用document.BeginInvoke(myDelegate)。

但是,如果它實際上是一個封裝GUI控件的一些其他組件,它可以暴露一些方法來調用BeginInvoke。檢查文檔(如果有的話)。如果沒有這種能力,那麼不幸的是,它可能只是沒有被設計爲支持多線程應用程序。

看起來你感到困惑的各種調用/ BeginInvoke的類型(可以理解)。這個較早的問題:What is the difference between Invoke and BeginInvoke?和Jon Skeets的答案應該有助於澄清事情。

+0

我明白了,但是當我調用'BeginInvoke'時,主持'document'的線程和另一個啓動移動對話框的線程被絞死。 – Graviton

相關問題