2009-06-10 133 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线程。