SerialPort在关闭后触发DataReceived事件

我在尝试停止SerialPort时遇到了一种奇怪的行为:DataReceived事件在取消订阅后以及在调用close之后继续触发! (请参阅以下代码中的StopStreaming )。 结果,在我的事件处理程序代码中,我得到一个InvalidOperationException,并显示“端口已关闭”的消息。

我错过了什么? 关闭端口和停止事件的正确方法是什么?

编辑:我每次运行代码时都会收到此错误。 所以这不是随机发生的竞争条件,而是一个系统性问题,表明代码完全破碎! 但是,我没有看到……

 private SerialPort comPort = new SerialPort(); public override void StartStreaming() { comPort.Open(); comPort.DiscardInBuffer(); comPort.DataReceived += comPort_DataReceived; } public override void StopStreaming() { comPort.DataReceived -= comPort_DataReceived; comPort.Close(); isStreaming = false; } private void comPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (e.EventType == SerialData.Chars) { SerialPort port = (SerialPort)sender; int N = comPort.BytesToRead; for (; N > 0; N--) { byte b = Convert.ToByte(comPort.ReadByte()); //... process b } } } 

编辑:按照建议,我将StopStreaming代码更改为以下内容:

 public override void StopStreaming() { comPort.DataReceived -= comPort_DataReceived; Thread.Sleep(1000); comPort.DiscardInBuffer(); Thread.Sleep(1000); comPort.Close(); isStreaming = false; } 

它现在似乎工作但我真的不高兴。 我希望有一种更有效的方法来删除回调,而不是在程序中插入hibernate期。

在线程池线程上调用DataReceived事件处理程序。 是的,他们有一个在不可预测的时间运行代码的尴尬习惯,这不是即时的。 因此,如果设备正在主动发送数据,它可以与您的Close()调用竞争并关闭它之后运行,这是相当不可避免的。 取消订阅不能修复它,线程池线程已经获得了它的目标方法。

确实意识到你正在做什么来触发这个问题,你在设备发送数据关闭端口。 这不是很好,它可以保证导致数据丢失。 但是,在调试代码时不太可能发生,因为您实际上并不关心数据。

对策是关闭握手,使设备不再发送任何东西。 丢弃输入缓冲区。 然后睡一会儿,一两秒钟,以确保飞行中的任何线程池线程都已完成运行。 然后关闭端口。 一个非常实用的方法就是不关闭端口,Windows会在您的进程终止时处理它。

看起来像multithreading问题。

SerialPort.DataReceived是在线程池的工作线程中引发的。 您正在从线程关闭端口,该端口与SerialPort.DataReceived引发的线程不同。

您可以处理InvalidOperationException或编写一些同步代码来解决此问题。

更新。

问题是,如果您的设备密集发送数据, SerialPort会将越来越多的工作项SerialPort队列。 即使您的代码在Close之前的任何时间都会睡眠,但这还不够。 不幸的是, SerialPort有一个丑陋的实现。 它没有一个选项,可以告诉“请,请用你的数据阻止我垃圾邮件”。

因此,具体的解决方案取决于设备的协议和握手参数。

我在一直在努力的应用程序中遇到了同样的问题。 在这里阅读线程池如何实现它是令人兴奋的。

在我追踪它的源代码之前,我发现将DataReceived事件处理程序的内容包含在预期问题中编写的try catch语句中是解决它的一种非常有效的方法。 现在我知道如果我还需要/想要在仍然接收数据的同时关闭SerialPort时,我无法做任何事情来防止这个问题,我对这种方法非常满意。