.NET SerialPort DataReceived事件线程与主线程的干扰

发布于 2024-10-08 17:31:03 字数 2439 浏览 0 评论 0原文

我正在使用 C# 中的 SerialPort 类编写一个串行通信程序,与通过 RS232 电缆连接的剥离机进行交互。当我将命令发送到机器时,它会根据命令响应一些字节。就像当我发送“\D”命令时,我期望将180字节的机器程序数据作为连续的字符串下载。根据机器手册,建议最佳实践是发送无法识别的字符,例如逗号 (,) 字符,以确保机器在发送周期中的第一个命令之前已初始化。我的串行通信代码如下:

public class SerialHelper
{
    SerialPort commPort = null;
    string currentReceived = string.Empty;
    string receivedStr = string.Empty;
    private bool CommInitialized()
    {
        try
        {
            commPort = new SerialPort();
            commPort.PortName = "COM1";
            if (!commPort.IsOpen)
                commPort.Open();
            commPort.BaudRate = 9600;
            commPort.Parity = System.IO.Ports.Parity.None;
            commPort.StopBits = StopBits.One;
            commPort.DataBits = 8;
            commPort.RtsEnable = true;
            commPort.DtrEnable = true;

            commPort.DataReceived += new SerialDataReceivedEventHandler(commPort_DataReceived);
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    void commPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        SerialPort currentPort = (SerialPort)sender;
        currentReceived = currentPort.ReadExisting();
        receivedStr += currentReceived;
    }

    internal int CommIO(string outString, int outLen, ref string inBuffer, int inLen)
    {
        receivedStr = string.Empty;
        inBuffer = string.Empty;
        if (CommInitialized())
        {
            commPort.Write(outString);
        }

        System.Threading.Thread.Sleep(1500);

        int i = 0;
        while ((receivedStr.Length < inLen) && i < 10)
        {
            System.Threading.Thread.Sleep(500);
            i += 1;
        }

        if (!string.IsNullOrEmpty(receivedStr))
        {
            inBuffer = receivedStr;
        }
        commPort.Close();

        return inBuffer.Length;

    }
}

我从 Windows 窗体调用此代码,如下所示:

len = SerialHelperObj.CommIO(",",1,ref inBuffer, 4)
len = SerialHelperObj.CommIO(",",1,ref inBuffer, 4)
If(inBuffer == "!?*O")
{
   len = SerialHelperObj.CommIO("\D",2,ref inBuffer, 180)
}

来自串行端口的有效返回值如下所示: \D00000010000000000010 550 3250 0000256000 等等......

我得到这样的东西: \D00000010D,, 000 550 D, 等等...

我觉得当我发送命令时我的通讯呼叫受到干扰。但我试图确保逗号命令的结果,然后启动实际命令。但接收到的线程正在插入上一个通信周期中的字节。

谁能解释一下这个......? 为了完成这项工作,我掉了很多头发。我不确定我哪里做错了

I am writing a serial communication program using the SerialPort class in C# to interact with a strip machine connected via a RS232 cable. When I send the command to the machine it responds with some bytes depending on the command. Like when I send a "\D" command, I am expecting to download the machine program data of 180 bytes as a continuous string. As per the machine's manual, it suggests as a best practice to send an unrecognized characters like comma (,) character to make sure the machine is initialized before sending the first command in the cycle. My serial communication code is as follows:

public class SerialHelper
{
    SerialPort commPort = null;
    string currentReceived = string.Empty;
    string receivedStr = string.Empty;
    private bool CommInitialized()
    {
        try
        {
            commPort = new SerialPort();
            commPort.PortName = "COM1";
            if (!commPort.IsOpen)
                commPort.Open();
            commPort.BaudRate = 9600;
            commPort.Parity = System.IO.Ports.Parity.None;
            commPort.StopBits = StopBits.One;
            commPort.DataBits = 8;
            commPort.RtsEnable = true;
            commPort.DtrEnable = true;

            commPort.DataReceived += new SerialDataReceivedEventHandler(commPort_DataReceived);
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    void commPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        SerialPort currentPort = (SerialPort)sender;
        currentReceived = currentPort.ReadExisting();
        receivedStr += currentReceived;
    }

    internal int CommIO(string outString, int outLen, ref string inBuffer, int inLen)
    {
        receivedStr = string.Empty;
        inBuffer = string.Empty;
        if (CommInitialized())
        {
            commPort.Write(outString);
        }

        System.Threading.Thread.Sleep(1500);

        int i = 0;
        while ((receivedStr.Length < inLen) && i < 10)
        {
            System.Threading.Thread.Sleep(500);
            i += 1;
        }

        if (!string.IsNullOrEmpty(receivedStr))
        {
            inBuffer = receivedStr;
        }
        commPort.Close();

        return inBuffer.Length;

    }
}

I am calling this code from a windows form as follows:

len = SerialHelperObj.CommIO(",",1,ref inBuffer, 4)
len = SerialHelperObj.CommIO(",",1,ref inBuffer, 4)
If(inBuffer == "!?*O")
{
   len = SerialHelperObj.CommIO("\D",2,ref inBuffer, 180)
}

A valid return value from the serial port looks like this:
\D00000010000000000010 550 3250 0000256000 and so on ...

I am getting something like this:
\D00000010D,, 000 550 D,, and so on...

I feel that my comm calls are getting interfered with the one when I send commands. But I am trying to make sure the result of the comma command then initiating the actual command. But the received thread is inserting the bytes from the previous communication cycle.

Can anyone please shed some light into this...?
I lost quite some hair just trying to get this work. I am not sure where I am doing wrong

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

还在原地等你 2024-10-15 17:31:03

串行端口不是线程安全的资源,使用它们的包装器或用于保存结果的字符串也不是线程安全的资源。发生的情况是 DataReceived 事件处理程序多次触发,最终导致多个处理程序同时执行。尝试向事件处理程序添加一个锁定块:

...

Object lockingObj = new Object();

...

void commPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    lock(lockingObj)
    {
        SerialPort currentPort = (SerialPort)sender;
        currentReceived = currentPort.ReadExisting();
        receivedStr += currentReceived;
    }
}

锁定语句可防止多个线程同时执行该块中的代码,因此receedStr 将在写入另一个ReadExisting 之前获取一个ReadExisting 的完整内容。

请注意,这并不能保证执行顺序;先入者获胜。因此,假设线程 1 首先到达这里,然后线程 2 250 毫秒后到达,线程 3 250 毫秒后到达。线程 1 可能会先进入,执行读取,这将需要一段时间,然后退出。此时,线程 2 和 3 进入该函数并在锁语句上被阻塞,等待线程 1 释放其锁。一旦发生这种情况,就取决于内核首先调度哪个线程,这可能是线程 3,具体取决于许多操作系统和硬件因素。

Serial ports are not thread-safe resources, and neither are the wrappers that work with them, or the string you're using to hold the results. What's happening is that the DataReceived event handler is firing multiple times, and you're ending up with multiple handlers executing at the same time. Try adding a lock block to your event handler:

...

Object lockingObj = new Object();

...

void commPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    lock(lockingObj)
    {
        SerialPort currentPort = (SerialPort)sender;
        currentReceived = currentPort.ReadExisting();
        receivedStr += currentReceived;
    }
}

The lock statement prevents more than one thread from executing code in the block at a time, so receivedStr will get the full contents of one ReadExisting before another is written to it.

Be aware that this DOES NOT guarantee an order of execution; first-in wins. So, let's say Thread 1 gets here first, then Thread 2 arrives 250ms later, and Thread 3 250ms after that. Thread 1 is likely going to enter first, perform the Read which will take a little while, then exit. In that time, Threads 2 and 3 enter the function and are blocked on the lock statement, waiting for Thread 1 to release its lock. Once that happens, it's down to which thread gets scheduled first by the kernel, and that could be Thread 3 depending on a number of OS and hardware factors.

荭秂 2024-10-15 17:31:03

为什么每次都要重新初始化端口?我觉得一个就够了。

Why are you re-initializing your port every time? I would think one is enough.

乄_柒ぐ汐 2024-10-15 17:31:03

我发现您的代码存在许多问题。

  1. 永远不要忽视异常。返回 false 表示忽略异常。当你这样做时,你不知道发生了哪种异常。
  2. 特别是,您的 commPort 对象可能从未被实例化。它可能处于任何状态,但您会忽略异常并继续访问可能无效的对象。
  3. 当端口打开时,DataReceived 事件可能随时发生。它可能会也可能不会在同一线程上引发。您的代码可能会从事件处理程序下关闭该端口,这将引发您的代码未捕获的异常。
  4. 这里可能并不重要,但是您是否知道字符串连接在 .NET 中是一个相对昂贵的操作?字符串是不可变的。 receivedStr += currentReceived 不会附加到 receivedStr - 它创建一个新的字符串对象来保存这两部分。

I see a number of problems with your code.

  1. Never ignore exceptions. Returning false is ignoring an exception. You have no idea which exception happened when you do that.
  2. In particular, your commPort object may never have been instantiated. It could be in any state at all, but you're ignoring exceptions and proceeding with accessing the potentially-invalid object.
  3. That DataReceived event can happen at any time while the port is open. It may or may not be raised on the same thread. Your code might be closing that port out from under the event handler, which would throw an exception that your code is not catching.
  4. It probably doesn't matter here, but did you know that string concatenation is a relatively expensive operation in .NET? Strings are immutable. receivedStr += currentReceived doesn't append to receivedStr - it creates a new string object to hold both pieces.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文