跨线程操作无效:从创建它的线程以外的线程访问控制

时间:2012-01-23 16:48:24

标签: c# .net winforms multithreading

我知道这个问题已被问过几次,但我似乎无法找到为什么在我的情况下这样做。

首先,我会解释一下我的程序。它通过FTDI芯片连接到硬件设备,因此它通过USB生成COM。我的程序启动,它是一个MDI接口。单击“连接”会显示一个类似于Windows中“添加设备”框的连接框。它扫描计算机上的所有COM并尝试连接到它,以报告它是什么类型的设备。然后,用户单击设备,连接到设备,并打开子窗体以控制该设备。

所以,我的问题是,我在那里有很多多线程。我第一次连接到我的设备时,它工作正常。第二次,它返回一个跨线程操作错误。

这是我的代码的简短示例:

    private void ConnectToolStripButton_Click(object sender, EventArgs e)
    {

        Dialogs.Connect Connect = new Dialogs.Connect();
        if (Connect.ShowDialog() == DialogResult.OK)
        {
            this.Connect(Connect.Connection);
        }

    }

    private void Connect(CommunicationInterfaces.Base Connection)
    {

        // Set the connection to the one the connect dialog gave us.
        Child NewConnection = new Child(Connection);

        // Set the parent of the new child and show it.
        NewConnection.MdiParent = this;
        NewConnection.Show(); // CRASH HERE!

    }

所以它在.show()上崩溃并出现以下错误,但只是第二次连接到它:Cross-thread operation not valid: Control 'Child' accessed from a thread other than the thread it was created on.

如果我没有弄错的话,那就是在UI线程上创建了Child(我的子表单的名称)对象。为什么它会给我一个跨线程操作错误呢?这是我儿童形式的问题吗?

更新:保持活动计时器

所以我已经能够更明确地指出这个问题了。问题在于我的孩子的形式中的Keep Alive主题。为了解释这种情况:我有一个需要保持活动的连接,所以我有一个线程运行每500ms来向我的设备发送一个特殊的标题。这是我保持活动的线程代码:

    private void Child_Shown(object sender, EventArgs e)
    {

        this.Connection.DataReceived += DisplayData;

        ...

    }

    private void DisplayData(object Sender, byte[] Data)
    {

        ...

                CreateFaultBox((FaultBoxes.Base.BoxTypes)Data[1]);

        ...

    }

    private void CreateFaultBox(FaultBoxes.Base.BoxTypes BoxType)
    {

        KeepAliveTimer = new System.Threading.Thread(new System.Threading.ThreadStart(this.KeepAlive));
        KeepAliveSwitch = true;
        KeepAliveTimer.Start();

        ...

    }

    private void KeepAlive()
    {

        while (Connection != null && KeepAliveSwitch)
        {

            Console.WriteLine("KEEP ALIVE");

            // Keep the connection alive.
            Connection.KeepAlive();

            // Wait 500ms for the next keep alive.
            System.Threading.Thread.Sleep(500);

        }

    }

如果我删除前3行,那么如果我不启动该线程,它的工作没有任何打嗝。当我关闭表单时,KeepAliveSwitch被设置为false,因此保持活动线程在下一个500ms的休眠期后终止。

解决方案

我将保持活动的线程更改为后台工作者。工作良好。但是我没有得到线程和后台工作者之间的区别,在这种情况下不应该两者都一样吗?

1 个答案:

答案 0 :(得分:4)

儿童表格中是否有任何线程?如果是这样,这是我的理论:

您可能会看到的是竞争条件,其中第一次显示客户端时,客户端表单正忙于连接到后台线程上的某个设备,而在此期间您的MDI父UI线程显示()s形式(因此拥有窗口句柄,一切都很好)。第二次显示客户端时,会得到一个缓存连接,因此子进程中的后台线程非常快速地连接,然后调用一些UI操作,可能会像使用InvokeRequired()的优秀开发人员一样进行检查。由于您的客户端表单还没有句柄,后台线程对于InvokeRequired变为false,然后调用并创建句柄本身。

所有这些都记录在Ivan Krivyakov的great post中。

因此,如果以上所有声音都正确,则只需在子窗体中启动后台工作,直到创建句柄。您可能希望将其挂在Form Shown event而不是构造函数上。