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

Posted

技术标签:

【中文标题】跨线程操作无效:控件从创建它的线程以外的线程访问【英文标题】:Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on 【发布时间】:2012-02-17 00:27:28 【问题描述】:

我知道这个问题已经被问过好几次了,但我似乎不太明白为什么在我的情况下会这样。

首先,我将解释一下我的程序。它通过 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 线程。为了解释这种情况:我有一个需要保持活动的连接,所以我有一个线程每 500 毫秒运行一次,以向我的设备发送一个特殊的标头。这是我的保持活动线程代码:

    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,因此在接下来的 500 毫秒睡眠期后,保持活动线程会终止。

解决方案

我将保持活动线程更改为后台工作线程。工作正常。但是我不明白线程和后台工作人员之间的区别,在这种情况下两者不应该相同吗?

【问题讨论】:

【参考方案1】:

子表单中是否有任何线程?如果是这样,这就是我的理论:

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

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

因此,如果以上所有内容都正确,那么在创建句柄之前不要在 Child 表单中启动后台工作。您可能希望将其挂在 Form Shown event 而不是构造函数上。

【讨论】:

这正是问题所在。在您的帮助下,我设法将它指向我的 DisplayData 事件(连接事件),我正在将处理程序添加到我孩子的构造函数中的事件中。但是,即使我在 Child 的 Shown 事件中添加了处理程序,它仍然给我带来了完全相同的问题。如果我删除事件处理程序,它会正常工作。我将尝试在我的活动中指出问题。我尝试添加 InvokeRequired 检查(因为我没有进行检查),但仍然不行。我会及时通知你的。非常感谢! 如果您添加该事件处理程序的代码并在哪里挂钩,我可能会提供进一步的帮助。 我已经添加了所有代码。哎呀,这个程序越来越大了!哈哈 每次从连接接收到任何数据时,您似乎都在启动一个新的保活线程。这是故意的吗? 只有在我第一次收到数据时,我才删除了 if。但我通过切换到后台工作人员使其工作,但并不真正理解其中的区别。

以上是关于跨线程操作无效:控件从创建它的线程以外的线程访问的主要内容,如果未能解决你的问题,请参考以下文章

跨线程操作无效:控件“chart1”从创建它的线程以外的线程访问

跨线程Winforms控件编辑[重复]

跨线程操作无效:控件'listBox1'从一个>线程访问,而不是它在[重复]上创建的线程

不一致的“跨线程操作无效”异常

C# 委托 / 跨线程访问UI / 线程间操作无效: 从不是创建控件“Form1”的线程访问它

访问从不同线程访问的控件时如何处理无效的跨线程操作?