内部控件上的“跨线程操作无效”exception

我一直在努力解决这个问题:我有一个function,旨在将控件添加到具有跨线程处理的面板,问题是虽然面板和控件在“InvokeRequired = false” – 我得到一个exception告诉我其中一个控件内部控件是从其创建的线程以外的线程访问的,该代码片段如下所示:

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 } 

exception消息如下:

“跨线程操作无效:控制’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 } } 

抛开 – 为了节省自己必须创建无数的委托类型:

 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面板是在主线程中创建的,所以调用将失败。

pnlFoo在哪里被创建,以及在哪个线程中? 你知道什么时候创建它的句柄吗? 如果它是在原始(非UI)线程中创建的,那就是问题所在。

应在同一个线程上创建和访问同一窗口中的所有控制句柄。 此时,您不需要检查是否需要Invoke,因为ctrlpanel应该使用相同的线程。

如果这没有帮助,请提供简短但完整的程序来certificate问题。

这是一段代码:

 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分支时,你调用了同样的函数,导致一个奇怪的递归情况。

在你自己的回答中,你说:

澄清事情:在基本线程上创建“panel”,在新线程上创建“ctrl”

我想这可能是你问题的原因。 应该在同一个线程(基础线程)上创建所有UI元素。 如果由于新线程中的某些操作而需要创建“ctrl”,则将事件激活回基本线程并在那里进行创建。

这里有很多有趣的答案,但Winform应用程序中任何multithreading的一个关键项是使用BackgroundWorker启动线程,并与Winform主线程进行通信。

这是完整程序的一小部分:

 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 } }