使用委托进行跨线程处理的 C# 问题

Posted

技术标签:

【中文标题】使用委托进行跨线程处理的 C# 问题【英文标题】:C# trouble using delegates for cross threading 【发布时间】:2016-05-07 06:42:04 【问题描述】:

我在使用委托从不是主窗体线程的线程中更改 Textbox 时遇到问题。

我有两个类,一个带有 UI 的主 Form1.cs 类和另一个类 LINClass.cs,我在其中编写了一个设备函数。 在 Form1 中,我启动了一个不断轮询设备的后台工作程序,以及另一个从设备中检索数据的线程(RXTask()),这两个线程的所有功能都来自 LINCLass.cs。

从设备检索数据的线程包含一个委托,该委托指向允许更改 Form1 文本框的 Form1.cs 函数:

public class LINClass : Form

    private delegate void FormUpdater(int devnum, string rpm, string current, string temp);

//some other variables and procedure

public void RXTask()

    FormUpdater frmUpdt = new FormUpdater(Form1.GUIupdate);
    //other procedures and a loop containing the invoke...
    this.Invoke(frmUpdt, new object[]devnum, rpm,
                                        current,
                                        temperature);


Form1类包含调用的方法,写法如下

public static void GUIupdate(int eWPnum, string rpm, string current, string temp)

    //take the parameters and write them in the textbox

现在,当我运行代码时,线程正在运行,但调用函数时出现异常。

http://s13.postimg.org/9ohuj9d7r/exception.png

它说,“InvalidOperationException 未被管理,在创建窗口句柄之前不能在控件上调用 Invoke 或 BeginInvoke”

【问题讨论】:

这是一个众所周知的问题。您需要谷歌 BeginInvoke 以获得规范的解决方案。基本上,您必须检查是否需要调用,如果是,则使用相同的参数调用它并停止在错误的线程上执行。 您可能在表单的Initialize() 方法完成运行之前调用了该方法。如果文本框尚未添加到 from 的控件集合中,您将收到此错误。 Windows 不是实时操作系统;您确定要尝试连续轮询设备吗?您是否愿意加热整个 CPU 以除此之外什么都不做?我的笔记本电脑的电池寿命不会感谢你。为什么不在一个线程上做所有事情并异步处理设备,而不是同时处理? 谢谢大家的建议,此时我在执行调用的代码之前用“蛮力”this.CreateHandle() 解决了这个问题,在这段代码之后用this.DestroyHandle() 解决了每个循环GUI 的句柄被创建和删除... 【参考方案1】:

您应该使用命令模式并将命令类从一个线程放入队列中,并让另一个线程从中读取。

【讨论】:

感谢您的回复,我是面向对象代码的新手,但我会研究您的建议! 您创建了一个基类和许多覆盖执行方法的子类。每个子类都有不同的执行方法。当您想要执行某个方法时,您可以创建一个特定对象并将其放入队列中。另一个线程逐个元素地读取队列并运行执行方法 我有一个疑问,所以我想问你另一个问题:我用一个名为 LINClass (RXThread = new Thread(new ThreadStart( LIN.RXTask));),RXThread可以访问实例的所有成员吗?或者换句话说,RXThread 正在处理实例 LIN? 应该可以,如果我理解正确的话。您应该始终小心线程锁,并且应该花几天时间来研究它。【参考方案2】:

您需要防止调用this.Invoke(),除非已创建表单的窗口。

最简单的方法是覆盖OnLoad() 并设置一个标志:

private bool isLoaded;

protected override void OnLoad(EventArgs e)

    base.OnLoad(e);
    Volatile.Write(ref isLoaded, true);

然后在调用之前检查标志:

public void RXTask()

    FormUpdater frmUpdt = new FormUpdater(Form1.GUIupdate);
    //other procedures and a loop containing the invoke...

    if (Volatile.Read(ref isLoaded))
    
        this.Invoke(frmUpdt, new object[]
        
            devnum, rpm,
            current,
            temperature
        );
    

(如果您的 .Net 版本没有 Volatile.Read()/Volatile.Write(),请将标志声明为 volatile。)

【讨论】:

这是一个非常不靠谱的建议,OnHandleCreated() 可以运行不止一次。当他遵循您的建议时,OP 很容易启动线程,那将非常糟糕。建议改为使用 Load 事件。 @HansPassant 我不知道可以多次调用 OnHandleCreated()。这是在哪里记录的?我在任何地方都找不到它(例如在documentation like this)。 自己尝试一下,例如更改 ShowInTaskbar 属性。我已经在很多帖子中记录了这一点:) @HansPassant 嗯,微软似乎有piled quite a lot of code into Control.OnHandleCreated()... @MatthewWatson 一般的答案是:每当调用Control.RecreateHandle() 时,都会额外调用该函数。其中一种情况是changing ShowInTaskbar

以上是关于使用委托进行跨线程处理的 C# 问题的主要内容,如果未能解决你的问题,请参考以下文章

C#如何使用异步编程

c# 多线程运行时出现主界面卡死现象如何解决?

c#如何跨线程调用窗体控件

C#如何使用异步编程BeginInvoke/EndInvoke

C#跨线程操作控件的最简单实现探究

C#异步编程