2011-11-01 73 views
4

我有一個在.NET Winforms中編碼的項目。 我需要執行數據挖掘操​​作,將文本打印到TextBox並更新進度。.NET BackGroundWorker - InvalidOperationException:跨線程操作無效

我試圖用BackgroundWorker的做的,但它拋出一個InvalidOperationException異常(跨線程操作無效:控制「XXXXX」從比它是在創建的線程以外的線程訪問)

要縮小我開始了一個新項目,其中包括以下內容: 按鈕 - 啓動BackgroundWorker 標籤 - 打印文本。 和ProgressBar。

但是,結果是一樣的。我搜索了特種部隊,並被告知使用代表,但我不熟悉它。

這是引發錯誤的代碼示例:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 

namespace TestProject 
{ 
    public partial class Form1 : Form 
    { 
     private readonly BackgroundWorker _bw = new BackgroundWorker(); 

     public Form1() 
     { 
      InitializeComponent(); 
      _bw.DoWork += RosterWork; 
      _bw.ProgressChanged += BwProgressChanged; 
      _bw.RunWorkerCompleted += BwRunWorkerCompleted; 
      _bw.WorkerReportsProgress = true; 
      _bw.WorkerSupportsCancellation = false; 
     } 

     private void RosterWork(object sender, DoWorkEventArgs doWorkEventArgs) 
     { 
      for (int i = 0; i < 1000; i++) 
      { 
       label1.Text = i.ToString(); 
       _bw.ReportProgress(Convert.ToInt32((i * (100/1000)))); 
      } 
     } 

     private void BwProgressChanged(object sender, ProgressChangedEventArgs e) 
     { 
      progressBar1.Value = e.ProgressPercentage; 
     } 

     private void btnStart_Click(object sender, EventArgs e) 
     { 
      progressBar1.Show(); 
      _bw.RunWorkerAsync(); 
     } 

     private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      progressBar1.Hide(); 
     } 

    } 
} 

更新: 我跟喬恩飛碟雙向的答案,它真的在我的測試項目工作,但回到我真正的項目,

我的佈局形式:

表 - 的TabControl - TAB1 -Tab1Panel -TextBox1

當達到這一行:

TextBox txtbox1 = new TextBox(); 
Tab1Panel.Controls.Add(txtbox1); 

的錯誤,當我添加文本框到面板控制編程仍時有發生。

最後,我代替這個:

if (Tab1Panel.InvokeRequired) 
    Tab1Panel.Invoke((MethodInvoker)delegate { Tab1Panel.Controls.Add(txtbox1); }); 
else 
    Tab1Panel.Controls.Add(txtbox1); 

一切工作。如何確定控件是InvokeRequired,它是否指定了控件?

回答

11

這就是問題所在:

label1.Text = i.ToString(); 

你試圖改變BackgroundWorker內標籤文本,這是不是在UI線程上運行。 BackgroundWorker的要點是要完成所有非UI工作,使用ReportProgress定期「返回」UI線程並使用您正在創建的進度更新UI。

所以要麼你需要BwProgressChanged改變label1.Text爲好,你需要使用Control.Invoke/BeginInvoke就像你從任何其他後臺線程:

// Don't capture a loop variable in a lambda expression... 
int copy = i; 
Action updateLabel =() => label1.Text = copy.ToString(); 
label1.BeginInvoke(updateLabel); 

更多關於複製部分,請參閱Eric Lippert的博文,"Closing over the loop variable considered harmful"。在這種情況下,這只是一個問題,因爲我使用的是BeginInvoke。這可以改爲剛:

Action updateLabel =() => label1.Text = i.ToString(); 
label1.Invoke(updateLabel); 

...但現在的背景工人將總是等待UI趕上之前繼續前進,這在現實生活中是通常不是你想。我通常比Invoke更喜歡BeginInvoke

+0

謝謝喬恩斯基特!我在Google上搜索.NET Threading。我讀了一些關於Thread/ThreadStart/ThreadPool/Delegate的文章。看起來它們都更像預期的線程編程標準。那麼使用BackgroundWorker是否正確? – Cheung

+0

你的意思是「哪一個不是**在UI線程上運行」? – Geoff

+0

@Geoff:是的,謝謝。 –

0

您正在從backgroundworker線程中訪問已在GUI線程上創建的標籤。 不允許從其他創建控件的線程訪問Windows控件;因此你有例外。

你不應該直接訪問標籤,而應該從你的線程中引發一個事件,並且確保它在正確的線程上被調用,它向UI發出信號,以便你可以更改標籤的內容。

3

使用此代碼IW將工作

BeginInvoke((MethodInvoker)delegate 
       { 
        TextBox1.Text += "your text here"; 

       });