有很多很多方法可以做到这一点。我将介绍使用自定义事件、委托和 Invoke() 的经典方法,因为我认为理解该过程很重要。一旦你明白了这一点,你就可以跳到一些较新的方法。
首先,在您的 SerialThing() 类中,您声明一个自定义事件以在收到数据时传递数据:
class SerialThing
{
public delegate void DataReceivedDelegate(string data);
public event DataReceivedDelegate DataReceived;
static SerialPort myDevice;
public SerialThing()
{
myDevice = new SerialPort();
myDevice.DataReceived += new SerialDataReceivedEventHandler(myDevice_DataReceived);
}
void myDevice_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// ... grab the data and place into a string called "data" ...
string data = "";
// raise our custom event:
if (DataReceived != null)
{
DataReceived(data);
}
}
}
现在,在 Form1 中,您在创建 SerialThing 实例时订阅该自定义事件。此外,当接收到该事件时,您可以使用 InvokeRequired、Invoke 和委托将来自辅助线程的调用编组到主线程:
public partial class Form1 : Form
{
SerialThing mySerialThing;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
mySerialThing = new SerialThing();
mySerialThing.DataReceived += new SerialThing.DataReceivedDelegate(mySerialThing_DataReceived);
}
private delegate void DataReceivedDelegate(string data);
void mySerialThing_DataReceived(string data)
{
if (this.InvokeRequired)
{
this.Invoke(new DataReceivedDelegate(mySerialThing_DataReceived), new Object[] { data });
}
else
{
textBox1.Text = data;
}
}
}
编辑:针对您的以下评论...
将委托视为简单的“指向方法的指针”。当您执行委托时,相关的方法就会运行。
InvokeRequired() 部分确定代码是否在与创建控件的线程不同的线程中运行。在这种情况下,控件是表单本身 ( this
)。如果返回 true,则事件是在不同的线程中接收的。然后我们继续this.Invoke()
在块的真实部分内排队If
。再次this
指Form。因此,表单请求在创建它的线程(主 UI 线程)上调用(“运行”)传递的委托。我们创建了一个委托的实例,它实际上指向了我们已经在导致递归调用的相同方法。第二个参数只是一个 Object 数组,用于与委托一起传递参数。
当 Invoke() 运行时,由于递归调用,我们最终会重新进入该方法。然而,此时 InvokeRequired() 检查将返回 false,因为我们现在正在主 UI 线程中运行。因此,我们下拉到If
我们更新 TextBox 的语句的错误部分。在这种模式中,更新语句else
块中的GUI 控件是安全的。If
请注意,这里不需要递归调用。这只是一种风格选择。我们本可以使用委托所指向的第二个“帮助”函数,然后调用它。递归方法减少了所需方法的数量。
这可能是解决此类问题的最冗长的方法。不过,我喜欢它,因为它显示了事件和数据的流动,以及线程之间的移动。
我们可以使用匿名委托将所有表单代码缩短到这样:
private void Form1_Load(object sender, EventArgs e)
{
mySerialThing = new SerialThing();
mySerialThing.DataReceived += delegate (string data)
{
this.Invoke((MethodInvoker)(delegate() { textBox1.Text = data; }));
};
}
我不了解你,但作为一名前 VB6 程序员,当你第一次看到这种类型的东西时,这看起来很奇怪。
我还使用了我知道在不同线程中运行的组件,但是“表单代码”从来不必使用委托的东西,所以也许有一些东西可以埋在类中?
是的,可以将一些“魔法”烘焙到一个类中,这样它就可以在主 UI 线程上引发事件,因此不需要任何 Invoke() 调用。一种方法是通过使用SynchronizationContext。
解决此类问题的另一种可能性是使用 BackgroundWorker() 控件,该控件具有在主 UI 线程中为您引发的诸如 ProgressChanged() 和 RunWorkerCompleted() 之类的事件(它们在后台执行必要的调用类型的东西为你)。