2009-06-10 80 views
7

我一直在爲此奮鬥了很長一段時間: 我有一個功能用於將控件添加到具有跨線程處理的面板,但問題是雖然面板和控件處於「InvokeRequired = false」狀態 - 我得到一個異常,告訴我其中一個控件內部控件是從其創建的線程以外的線程訪問的,片段如下所示:內部控件的「跨線程操作無效」異常

public delegate void AddControlToPanelDlgt(Panel panel, Control ctrl); 
    public void AddControlToPanel(Panel panel, Control ctrl) 
    { 
     if (panel.InvokeRequired) 
     { 
      panel.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl); 
      return; 
     } 
     if (ctrl.InvokeRequired) 
     { 
      ctrl.Invoke(new AddControlToPanelDlgt(AddControlToPanel),panel,ctrl); 
      return; 
     } 
     panel.Controls.Add(ctrl); //<-- here is where the exception is raised 
    } 

異常消息是這樣的:

「交叉線程操作無效:控制「pnlFoo」從比它的」

創建的線程以外的線程訪問(‘pnlFoo’是ctrl.Controls下)

我如何添加Ctrl鍵面板? !


當代碼到達「panel.Controls.Add(ctrl);」時,行 - 面板和ctrl「InvokeRequired」屬性設置爲false,問題是控件裏面的ctrl「InvokeRequired」設置爲true。爲了澄清一些事情:在基線上創建「panel」,在新線程上創建「ctrl」,因此必須調用「panel」(導致「ctrl」需要再次調用)。一旦兩個調用完成,它就會到達panel.Controls.Add(ctrl)命令(在這個狀態下「panel」和「ctrl」都不需要調用)

這是一個完整的小代碼片段程序:

public class ucFoo : UserControl 
{ 
    private Panel pnlFoo = new Panel(); 

    public ucFoo() 
    { 
     this.Controls.Add(pnlFoo); 
    } 
} 

public class ucFoo2 : UserControl 
{ 
    private Panel pnlFooContainer = new Panel(); 

    public ucFoo2() 
    { 
     this.Controls.Add(pnlFooContainer); 
     Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner()); 
     t.Start() 
    } 

    private AddFooControlToFooConatiner() 
    { 
     ucFoo foo = new ucFoo(); 
     this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised 
    } 
} 
+0

這裏是程序: public class ucFoo:UserControl { private Panel pnlFoo = new Panel(); public ucFoo() { this.Controls.Add(pnlFoo); } } public class ucFoo2:UserControl { private Panel pnlFooContainer = new Panel(); public ucFoo2() {this.Controls.Add(pnlFooContainer);線程t =新線程(new ThreadStartAddFooControlToFooConatiner()); t.Start(); } private AddFooControlToFooConatiner() { ucFoo foo = new ucFoo(); this.pnlFooContainer.Controls.Add(ucFoo); // < - 這是發生異常的地方 } } – Nissim 2009-06-10 09:09:57

+0

我添加了片段作爲更好閱讀的答案 – Nissim 2009-06-10 09:13:16

回答

3

pnlFoo哪裏在創建,並在哪個線程?你知道它的句柄何時被創建嗎?如果它是在原始(非UI)線程中創建的,那就是問題所在。

應該在同一個線程上創建和訪問同一窗口中的所有控制手柄。此時,您不應該需要兩個檢查是否需要Invoke,因爲ctrlpanel應該使用相同的線程。

如果這沒有幫助,請提供一個簡短但完整的程序來演示問題。

+1

這實際上並非如此。控制*對象*可以在任何線程上創建,其*柄*不能在其他線程上創建。由於OP實際上正在處理這個細節,所以這個區別就更重要了。 – 2009-06-10 09:09:05

+1

感謝Remus - 將會編輯。 – 2009-06-10 09:23:16

+0

(如果你可以檢查編輯是否正確,這將有很大幫助。) – 2009-06-10 09:23:52

1

在你自己的答案你的狀態:

爲了澄清事情:「面板」的基礎線程上創建並在新線程

我想這可能是「CTRL」你的問題的原因。所有的UI元素應該在同一個線程上創建(基本的)。如果您需要創建「ctrl」作爲新線程中某些操作的結果,那麼將事件發回到基線並在那裏進行創建。

3

順便說一句 - 拯救你自己不必創建無數的委託類型:

if (panel.InvokeRequired) 
{ 
    panel.Invoke((MethodInvoker) delegate { AddControlToPanel(panel,ctrl); }); 
    return; 
} 

此外,這不現在就來AddControlToPanel內調用定期檢查靜電,所以你不能把它錯了。

3

必須在同一個線程上創建'panel'和'ctrl',即。你不能有panel.InvokeRequired返回不同於ctrl.InvokeRequired的值。這是如果面板和Ctrl都有創建的句柄或屬於容器與創建的句柄。從MSDN

如果控件的句柄尚不存在 ,InvokeRequired直到找到 確實有一個 窗口句柄控件或窗體搜索了 控件的父鏈。如果找不到合適的 句柄,則InvokeRequired方法返回false。

因爲它是正確的,現在你的代碼是開放競爭情況,因爲,因爲尚未創建面板中的panel.InvokeNeeded可以返回false,那麼ctrl.InvokeNeeded一定會返回false,因爲很有可能Ctrl爲沒有添加任何容器,然後到達panel.Controls.Add時,面板在主線程中創建,因此調用將失敗。

1

這裏是一個代碼塊的工作:

public delegate void AddControlToPanelDlg(Panel p, Control c); 

     private void AddControlToPanel(Panel p, Control c) 
     { 
      p.Controls.Add(c); 
     } 

     private void AddNewContol(object state) 
     { 
      object[] param = (object[])state; 
      Panel p = (Panel)param[0]; 
      Control c = (Control)param[1] 
      if (p.InvokeRequired) 
      { 
       p.Invoke(new AddControlToPanelDlg(AddControlToPanel), p, c); 
      } 
      else 
      { 
       AddControlToPanel(p, c); 
      } 
     } 

這裏是我如何測試它。你需要有2個按鈕和一個FlowLayoutPanel的(我選擇了這個,所以我沒有去在乎位置H完成dinamically在面板中添加控件)

private void button1_Click(object sender, EventArgs e) 
     { 
      AddNewContol(new object[]{flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString())}); 
     } 

     private void button2_Click(object sender, EventArgs e) 
     { 
      ThreadPool.QueueUserWorkItem(new WaitCallback(AddNewContol), new object[] { flowLayoutPanel1, CreateButton(DateTime.Now.Ticks.ToString()) }); 
     } 

我,他probem與你的exaple是一種形式當你進入InvokeRequired分支時,你會調用相同的函數,導致一個奇怪的recurssion情況。

0

這裏是整個計劃的一小片段:

public class ucFoo : UserControl 
{ 
    private Panel pnlFoo = new Panel(); 

    public ucFoo() 
    { 
     this.Controls.Add(pnlFoo); 
    } 
} 

public class ucFoo2 : UserControl 
{ 
    private Panel pnlFooContainer = new Panel(); 

    public ucFoo2() 
    { 
     this.Controls.Add(pnlFooContainer); 
     Thread t = new Thread(new ThreadStart(AddFooControlToFooConatiner()); 
     t.Start() 
    } 

    private AddFooControlToFooConatiner() 
    { 
     ucFoo foo = new ucFoo(); 
     this.pnlFooContainer.Controls.Add(ucFoo); //<-- this is where the exception is raised 
    } 
} 
1

很多在這裏有趣的答案,但一個關鍵項目在一個winform應用程序的任何多線程使用BackgroundWorker啓動線程,並傳送回主要的Winform線程。