[英]“Cross-thread operation not valid” exception on inner controls
我一直在努力解決這個問題:我有一個功能,旨在將控件添加到具有跨線程處理的面板,問題是盡管面板和控件在“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);”時 line - panel和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
}
}
pnlFoo
在哪里創建,以及在哪個線程中? 你知道什么時候創建它的句柄嗎? 如果它是在原始(非UI)線程中創建的,那就是問題所在。
應在同一個線程上創建和訪問同一窗口中的所有控制句柄。 在這一點上,你不應該需要調用對於是否需要兩次檢查,因為ctrl
和panel
應該使用相同的線程。
如果這沒有幫助,請提供簡短但完整的程序來證明問題。
順便說一句 - 為了節省自己必須創建無數的委托類型:
if (panel.InvokeRequired)
{
panel.Invoke((MethodInvoker) delegate { AddControlToPanel(panel,ctrl); } );
return;
}
此外,現在對AddControlToPanel
的內部調用進行常規靜態檢查,因此您不會錯。
'panel'和'ctrl'必須在同一個線程上創建,即。 你不能有panel.InvokeRequired返回不同於ctrl.InvokeRequired的值。 也就是說, 如果panel和ctrl都創建了句柄,或者屬於創建了句柄的容器 。 來自MSDN :
如果控件的句柄尚不存在,InvokeRequired將向上搜索控件的父鏈,直到找到具有窗口句柄的控件或表單。 如果找不到合適的句柄,則InvokeRequired方法返回false。
因為它現在是你的代碼對競爭條件開放,因為panel.InvokeNeeded
可以返回false因為尚未創建面板,然后ctrl.InvokeNeeded
肯定會返回false,因為很可能ctrl尚未添加到任何容器然后通過到達panel.Controls.Add
面板是在主線程中創建的,所以調用將失敗。
這里有很多有趣的答案,但Winform應用程序中任何多線程的一個關鍵項是使用BackgroundWorker啟動線程,並與Winform主線程進行通信。
在你自己的回答中你說:
澄清事情:在基本線程上創建“panel”,在新線程上創建“ctrl”
我想這可能是你問題的原因。 應該在同一個線程(基礎線程)上創建所有UI元素。 如果由於新線程中的某些操作需要創建“ctrl”,則將事件激活回基本線程並在那里進行創建。
這是一段代碼:
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的表單(我選擇了這個,所以我不需要關心位置,但在面板中添加控件時)
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()) });
}
我探究你的問題是當你進入InvokeRequired分支時,你調用了同樣的函數,導致了一個奇怪的遞歸情況。
這是完整程序的一小部分:
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
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.