2

我收到了一個無意義的錯誤。來自BackgroundWorker2_RunWorkerCompleted in C#的無效跨線程操作

Cross-thread operation not valid: Control 'buttonOpenFile' accessed from a thread other than the thread it was created on.

在我的應用程序,用戶界面線程打完backgroundWorker1,當斷backgroundWorker2幾乎完全火災和等待它完成它。 backgroundWorker1在完成之前等待backgroundWorker2完成。 AutoResetEvent變量用於標記每個工人何時完成。在backgroundWorker2_RunWorkerComplete中調用了一個重置​​表單控件的函數。它在這個ResetFormControls()函數中拋出異常。我認爲在RunWorkerCompleted函數中修改表單控件是安全的。這兩個後臺工作人員都是從UI線程實例化的。這裏是我正在做的一個大大概括的版本:

AutoResetEvent evtProgrammingComplete_c = new AutoResetEvent(false); 
    AutoResetEvent evtResetComplete_c = new AutoResetEvent(false); 

    private void ResetFormControls() 
    { 
    toolStripProgressBar1.Enabled = false; 
    toolStripProgressBar1.RightToLeftLayout = false; 
    toolStripProgressBar1.Value = 0; 

    buttonInit.Enabled = true; 
    buttonOpenFile.Enabled = true; // Error occurs here. 
    buttonProgram.Enabled = true; 
    buttonAbort.Enabled = false; 
    buttonReset.Enabled = true; 
    checkBoxPeripheryModule.Enabled = true; 
    checkBoxVerbose.Enabled = true; 
    comboBoxComPort.Enabled = true; 
    groupBoxToolSettings.Enabled = true; 
    groupBoxNodeSettings.Enabled = true; 
    } 

    private void buttonProgram_Click(object sender, EventArgs e) 
    { 
    while (backgroundWorkerProgram.IsBusy) 
     backgroundWorkerProgram.CancelAsync(); 

    backgroundWorkerProgram.RunWorkerAsync(); 
    } 

    private void backgroundWorkerProgram_DoWork(object sender, DoWorkEventArgs e) 
    { 
    // Does a bunch of stuff... 

    if (tProgramStat_c == eProgramStat_t.DONE) 
    { 
     tProgramStat_c = eProgramStat_t.RESETTING; 

     while (backgroundWorkerReset.IsBusy) 
      backgroundWorkerReset.CancelAsync(); 

     backgroundWorkerReset.RunWorkerAsync(); 
     evtResetComplete_c.WaitOne(LONG_ACK_WAIT * 2); 

     if (tResetStat_c == eResetStat_t.COMPLETED) 
      tProgramStat_c = eProgramStat_t.DONE; 
    } 
    } 

    private void backgroundWorkerProgram_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
    // Updates form to report complete. No problems here. 

    evtProgrammingComplete_c.Set(); 
    backgroundWorkerProgram.Dispose(); 
    } 

    private void backgroundWorkerReset_DoWork(object sender, DoWorkEventArgs e) 
    { 
    // Does a bunch of stuff... 

    if (tResetStat_c == eResetStat_t.COMPLETED) 
     if (tProgramStat_c == eProgramStat_t.RESETTING) 
      evtProgrammingComplete_c.WaitOne(); 
    } 

    private void backgroundWorkerReset_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
    { 
    CloseAllComms(); 
    ResetFormControls(); 
    evtResetComplete_c.Set(); 
    backgroundWorkerReset.Dispose(); 
    } 

任何想法或建議,你可能會感激。我正在使用Microsoft Visual C#2008速成版。謝謝。

回答

5

RunWorkerCompleted將在啓動BackgroundWorker的線程上執行。由於您正在鏈接BackgroundWorkers(從1開始2),所以2的RunWorkerCompleted正在1的線程上執行,而不是UI線程。

您需要使用Invoke編組回到UI線程或將UI更新移動到1的RunWorkerCompleted。

我的建議是在更新UI時總是檢查InvokeRequired,這樣你就不用擔心它來自哪個線程。

+0

從技術上講,2的RunWorkerCompleted將在完全不同的ThreadPool線程上運行,而不是在1的線程上運行。 – 2010-05-12 16:07:29

+0

謝謝,Ragoczy。因此,如果我理解正確,'RunWorkerCompleted'運行在調用'RunWorkerAsync'的線程上,而不一定是實例化(創建/聲明)BackgroundWorker的線程。那是對的嗎? – 2010-05-12 16:10:10

+0

RunWorkerCompleted似乎在隨機線程池線程上運行(我的快速測試恰好結束了重用相同的線程)除非RunWorkerAsync從主線程和實現ISynchronizeInvoke的類中調用 - 有趣的是,兩者似乎都是必需的有限的測試案例,可能還有更多。無論如何,如果您的ResetFormControls方法被更改爲檢查InvokeRequired和Invoke(如果有必要),您應該可以 - 因爲那樣可以讓您在將來實現其他線程選項(.net 4),同時保持您的Form接口線程安全。 – Ragoczy 2010-05-12 16:40:31

1

BackgroundWorker對象不能嵌套。我建議儘可能使用.NET 4.0 Tasks,因爲它們是嵌套的。

嵌套BGWs只能使用類似Nito.Async庫中的ActionDispatcher。

+0

謝謝,斯蒂芬,但我的應用程序與嵌套的背景工作者(就「背景」任務而言)表現良好。我遇到的問題是在執行'RunWorkerCompleted'時更新窗體控件。 – 2010-05-12 16:13:12

+0

+1 - 在此之前沒有聽說過新的Tasks命名空間 - 它看起來非常有用。 – 2010-05-12 16:39:49

+0

任務岩石。併發收藏和一切。 .Net 4將使人們更容易使用魔鬼的工作......呃線程化。 – Ragoczy 2010-05-12 16:42:17

0

在另一個線程中訪問控件永遠不會線程安全。

你的問題是:「我如何使用另一個線程訪問由UI線程創建的控件?」

答案是通過調用你的控件。這裏有一個link,你可以看到一個允許它的代碼示例。

這是做你所需要的嗎?