2014-08-29 62 views
0

我有一個winforms應用程序,我在主窗體的Application.Run之前運行BackgroundWorker'RunWorkerCompleted'在錯誤線程上執行

BackgroundWorker的完成後,在其RunWorkerCompleted處理程序 - 它訪問的主要形式,而我得到的異常:

「跨線程操作無效:控制‘Form1的’從訪問 線程不是它創建的線程。「

因此,我認爲錯誤曾與this comment其中指出RunWorkerCompleted

做「只會讓UI線程上提出,如果UI線程創建的 BGW實例。」

(雖然它不是似乎像它是在一個單獨的線程上創建的)。 (並且也參見this comment there)。

所以我創建了一個簡單的測試,我BW.RunWorkerAsync();之前Application.Run(在「計劃」),並在那裏工作的罰款。拋出異常。

那麼可能是什麼問題?爲什麼與主窗體進行交互會拋出異常,但我正在從同一個線程運行BackgroundWorker

(我不能在這裏張貼整個代碼,因爲它是很長,發帖只是相關的代碼就是我前面提到的 - 。它拋出異常)

編輯

所以也許更具體的問題可以到位:如何讓「UI線程創建BGW」?它是否必須在Application.Run中?顯示錶單後?是否不是取決於哪個線程創建了的BGW,而是哪個線程調用RunWorkerAsync?

EDIT 2

檢查Thread.CurrentThread.ManagedThreadId我看到它的 RunWorkerAsync之前(因爲它是DoWork +=RunWorkerCompleted +=前),但的RunWorkerCompleted處理內。

當單步執行代碼並在RunWorkerAsync()之後等待幾秒鐘 - 它們都具有相同的線程ID,並且運行良好(所以不僅偶然,正確的線程被選中)!

+0

你試過調用形式? (另外,你正在尋找一個解決方案或解釋) – Sayse 2014-08-29 10:21:41

+0

我試圖更好地理解它,所以我可以修復它不_need_「調用」(並避免在未來更多的陷阱)。 – ispiro 2014-08-29 10:22:23

+7

*我不能在這裏發佈整個代碼,因爲它很長。只發布相關的代碼就是我之前提到的 - 它不會拋出異常*那麼您希望我們如何回答?即使我們這樣做,它將純粹是一種**猜測**,沒有一些代碼可以再現問題。 – 2014-08-29 10:25:55

回答

0

下面是其工作的例子:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Drawing; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows.Forms; 

namespace WindowsFormsApplication2 
{ 
    static class Program 
    { 
     /// <summary> 
     /// The main entry point for the application. 
     /// </summary> 
     [STAThread] 
     static void Main() 
     { 
      Form1 form = null; 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 

      BackgroundWorker workerThread = new BackgroundWorker(); 
      workerThread.DoWork += delegate 
      { 
       Thread.Sleep(1500); 
      }; 
      workerThread.RunWorkerCompleted += delegate 
      { 
       if (form != null) 
        form.BackColor = Color.Red; 
      }; 
      workerThread.RunWorkerAsync(); 

      form = new Form1(); 
      Application.Run(form); 
     } 

    } 
} 

好,它有一些種族問題。但我沒有看到任何問題。運行和主線程是相同的UI線程 - 您可以在調試中檢查它。 如何製造異常?

+0

這不會在UI線程中運行完成的事件。 – Servy 2014-08-29 14:06:19

+0

可笑。嘗試運行和調試它,你會發現它實際上是一個UI線程。也沒有例外,爲什麼?因爲它的UI線程! ) – norekhov 2014-08-30 18:45:11

+0

不,它不會,因爲當你啓動worker來捕獲時沒有當前的同步上下文,所以它會在後臺線程中觸發完成的事件。 – Servy 2014-09-02 13:48:56

2

強烈暗示您從錯誤的線程調用RunWorkerAsync()。 BGW需要確定哪個線程在其上運行其事件。它本身無法做到,它需要幫助。你可以簡單地添加一些診斷代碼到你的程序以確認此幫助提供:

public static class DebugUtils { 
    public static void CheckThreadState() { 
     if (System.Threading.SynchronizationContext.Current == null) { 
      throw new InvalidOperationException("You are on the wrong thread") 
     } 
    } 
} 

而且插入在你打電話的RunWorkerAsync(代碼的所有地方)這一呼籲:

DebugUtils.CheckThreadState(); 
0

BGW不能在UI線程中神奇地運行代碼。它需要有一些機制,通過它知道UI線程,以便它可以封送事件處理程序。

它實際使用的是SynchronizationContext.Current。當你調用RunWorkerAsync然後使用該上下文來封送事件處理程序時,它會「記住」當前的上下文。這是對Application.Run的調用,它爲您的UI的消息循環創建同步上下文,所以既然您在啓動工作者之前甚至沒有消息循環或同步上下文,它也無法在那裏封送代碼。

您需要等待才能啓動後臺工作人員,直到您確實收到了消息循環。這樣做的一個簡單的方法是在窗體的事件,直到消息循環已經建立,如Load事件,將不會觸發啓動它:

form.Load += (s, args) => worker.RunWorkerAsync(); 
+0

這聽起來合乎邏輯。但是,正如我所說 - 我嘗試了一個簡單的例子 - 在Application.Run之前運行'RunWorkerAsync();'並且運行良好。 – ispiro 2014-08-29 14:19:03

+0

@ispiro可能會發生任何數量的事情。在沒有真正知道你做了什麼的情況下,沒有什麼可說的。你可以抑制異常,而不需要實際封送到UI線程,當你認爲自己是用戶的時候,你不能修改UI對象,你可能已經以一種確實創建了上下文的方式來設置事物,等等 – Servy 2014-08-29 14:22:20