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

时间:2009-06-10 08:27:35

标签: c# winforms multithreading

我一直在努力解决这个问题: 我有一个函数旨在通过跨线程处理向面板添加控件,问题是尽管面板和控件在“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
    }
}

7 个答案:

答案 0 :(得分:3)

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

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

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

答案 1 :(得分:3)

顺便说一句 - 为了节省自己必须创建无数的委托类型:

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

此外,现在对AddControlToPanel的内部调用进行定期静态检查,因此您不会错误。

答案 2 :(得分:3)

必须在同一个线程上创建

'panel'和'ctrl',即。你不能有panel.InvokeRequired返回不同于ctrl.InvokeRequired的值。如果panel和ctrl都创建了句柄或者属于创建了句柄的容器,那就是。来自MSDN

  

如果控件的手柄还没有   现在,InvokeRequired搜索了   控制的父链直到找到   控件或表格确实有   窗把手。如果不合适   手柄可以找到,   InvokeRequired方法返回false。

现在你的代码对竞争条件开放因为panel.InvokeNeeded可以返回false因为尚未创建面板,所以ctrl.InvokeNeeded肯定会返回false因为很可能ctrl尚未添加到任何容器中,然后当你到达panel.Controls.Add时,面板是在主线程中创建的,所以调用将失败。

答案 3 :(得分:1)

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

  

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

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

答案 4 :(得分: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的表单(我选择了这个,所以我不需要关心位置,而不是在面板中添加控件)

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

答案 5 :(得分:1)

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

答案 6 :(得分: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
    }
}