SerialPort在关闭后触发DataReceived事件

时间:2012-12-14 09:53:06

标签: c# .net serial-port

我在尝试停止SerialPort时遇到了一种奇怪的行为:在取消订阅和调用close之后,DataReceived事件继续激活! (请参阅以下代码中的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;
}

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

3 个答案:

答案 0 :(得分:5)

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

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

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

答案 1 :(得分:1)

看起来像多线程问题。

从线程池中的工作线程中引发了

SerialPort.DataReceived。您正在从线程关闭端口,这与线程不同,其中SerialPort.DataReceived引发了。

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

<强>更新

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

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

答案 2 :(得分:0)

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

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