[英]“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.