【问题标题】:C# serial port loop in two thread sync problemC#串口循环中的两个线程同步问题
【发布时间】:2021-05-19 15:06:41
【问题描述】:

我是 C# 的初学者,想要一些关于如何解决以下问题的建议:

我的主要代码包括2个线程,第一个线程用于发送数据,第二个用于从串口读取数据。我用 sinh=1;变量以同步两个线程。 sinh = 1 的第一个线程发送第一个寄存器的寄存器名称和读取命令并设置 sinh = 2。然后第二个线程读取数据并设置 sinh = 3。然后 sinh = 3 的第一个线程发送寄存器名称和第二个读取命令注册并设置sinh = 4。最后,sinh = 4 处的第二个线程读取数据并设置sinh = 1,一切都再次重复。

问题是第二个线程没有按应有的方式读取数据。在开始发送和读取工作在几个周期后同步读取数据(应写入 sin = 2 的数据写入 sihn = 4,应写入 sin = 4 的数据写入 sihn = 2),然后它再次正常工作几个周期,然后再次混合所有数据等等。

我正在解决这个问题好几天了,我不知道该怎么办。

第一个线程(发送数据):

private void read()
{
        while (read_data_on)
    {
        if (sinh == 1 )
          {

            serialPort1.Write(new byte[] { 0x55, 0x40, 0x05 }, 0, 3); //set register 1
            serialPort1.Write(new byte[] { 0x55, 0x1c }, 0, 2); //read register 1
            sinh = 2;
        }
         if (sinh == 3 )
        {
            serialPort1.Write(new byte[] { 0x55, 0x40, 0x65 }, 0, 3); //set register 2
            serialPort1.Write(new byte[] { 0x55, 0x1c }, 0, 2); //read register 2
            sinh = 4;
        }
     }

第二个线程(接收数据):

private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) 
{
        if (sinh == 2 ) //read register 1
       {
           byte[] input1 = new byte[3];
           int st_bajtov1 = serialPort1.Read(input1, 0, 3);

               vrednost1 = (input1[2] << 16) | (input1[1] << 8) | (input1[0]);
               sinh = 3;                    
        }

       if (sinh == 4 ) //read register 2
       {
           byte[] input2 = new byte[3];
           int st_bajtov2 = serialPort1.Read(input2, 0, 3);

             vrednost2 = (input2[2] << 16) | (input2[1] << 8) | (input2[0]);
             sinh = 1;
       }
   }

【问题讨论】:

    标签: c# multithreading while-loop serial-port


    【解决方案1】:

    老实说,我不清楚你为什么要打扰这两个线程。正在写入的第一个线程什么都不做,它永远不会返回给调用者,显然你不希望它写入更多数据,直到它收到来自先前写入数据的响应,所以我认为整个事情可以在单个线程中进入单个循环。

    也就是说……

    主要问题是在写入数据的线程和接收数据的事件处理程序之间存在竞争。例如,如果您的串行设备响应使用sinh == 1 发送的数据并引发DataReceived 事件,则在发送线程有机会将sinh 设置为2 之前,事件处理程序将忽略接收到的数据。

    其次,您的代码也无法检查从端口读取的字节数。这可能会导致您在尝试处理之前无法实际读取三个字节的完整响应,因为您可能会在所有三个字节都可供读取之前获得DataReceived 事件。

    您可以通过引入锁和同步操作来解决第一个问题。但恕我直言,这并不是现代async/await 时代的最佳方法。相反,您应该使用 BaseStream 属性阅读并使用异步 API,以便只有一种方法,即除非需要,否则不会消耗 任何 线程。例如:

    private async Task read()
    {
        Stream stream = serialPort1.BaseStream;
    
        while (read_data_on)
        {
            stream.Write(new byte[] { 0x55, 0x40, 0x05 }, 0, 3); //set register 1
            stream.Write(new byte[] { 0x55, 0x1c }, 0, 2); //read register 1
            vrednost1 = await ReadInt24(stream); 
    
            stream.Write(new byte[] { 0x55, 0x40, 0x65 }, 0, 3); //set register 2
            stream.Write(new byte[] { 0x55, 0x1c }, 0, 2); //read register 2
            vrednost2 = await ReadInt24(stream); 
        }
    }
    
    private async Task<int> ReadInt24(Stream stream)
    {
        byte[] input = new byte[3];
        int offset = 0;
    
        while (offset < input.Length)
        {
            offset += await stream.ReadAsync(input, offset, input.Length - offset);
        }
    
        return (input[2] << 16) | (input[1] << 8) | (input[0]);
    }
    

    显然,您还需要更改SerialPort 对象的初始化,以便不再订阅DataReceived 事件。有了上面的,你就不需要了。

    同样,正如我所提到的,至少考虑到您在问题中发布的代码,实际上您可能根本不需要任何异步方面。如果您已经将整个线程提交给写入操作,并且您希望在读取响应之前不会写入更多数据,您可以执行与我上面的示例相同的操作,除非没有所有 async/await 内容:

    private void read()
    {
        Stream stream = serialPort1.BaseStream;
    
        while (read_data_on)
        {
            stream.Write(new byte[] { 0x55, 0x40, 0x05 }, 0, 3); //set register 1
            stream.Write(new byte[] { 0x55, 0x1c }, 0, 2); //read register 1
            vrednost1 = ReadInt24(stream); 
    
            stream.Write(new byte[] { 0x55, 0x40, 0x65 }, 0, 3); //set register 2
            stream.Write(new byte[] { 0x55, 0x1c }, 0, 2); //read register 2
            vrednost2 = ReadInt24(stream); 
        }
    }
    
    private int ReadInt24(Stream stream)
    {
        byte[] input = new byte[3];
        int offset = 0;
    
        while (offset < input.Length)
        {
            offset += stream.Read(input, offset, input.Length - offset);
        }
    
        return (input[2] << 16) | (input[1] << 8) | (input[0]);
    }
    

    (同样,还要确保删除对 `DataReceived 的订阅。)

    如果您想从 UI 线程启动和监控串行 I/O,则最好使用异步版本。但是非异步版本可以很好地作为您现在似乎拥有的插件替代品。

    在任何一种情况下,sinh 状态变量和所有来回线程的东西在你的例子中对我来说似乎都没有用。它只是使事情复杂化,更容易引入错误,而没有添加任何有益的东西。所以最好的解决方案是完全省略所有这些。 :)

    【讨论】:

    • 嘿,谢谢你的建议。您的代码运行良好,但在两个线程中非常慢(每秒 60 次测量)。我在一个线程中尝试我的代码并且工作得很好,大约 800(每秒测量),但我的代码中没有等待逻辑,所以我的所有数据都在混合。你能写出只在一个线程中工作的代码吗?
    • 上面的第二个选项应该在一个线程中完成所有工作。您的帖子中没有其他内容可以解释为什么“数据正在混合”,因此无法知道可以进行哪些更改来阻止这种情况的发生。以上与您发布的代码在语义上完全相同相同,但同步正确,因此如果仍然存在“混合”问题,则它之前存在其他原因。您是否考虑过在循环之前插入代码以从串口读取任何可用字节,以确保串口缓冲区中没有剩余内容?
    • 我确信数据在代码中混合在一起。您的代码运行良好,但由于在线程外调用函数而非常慢。你能给我一个代码示例吗,在 read() while 循环中都有写入,并且没有线程外的调用函数(例如:vrednost1 = ReadInt24(stream); 不受欢迎)。非常感谢。
    • 当线程外的函数调用“vrednost1 = ReadInt24(stream);”时,有16ms的延迟,这是现在的主要问题。
    • 我不理解你,对不起。第二个示例仅使用一个线程。为什么您反对将三个字节组合成一个适当的int 值作为单独的方法的重复方面?这对性能的影响。如果循环执行不止一次,那是因为它必须。如果您的代码不执行相同的逻辑,但速度更快,那么您会以得到不正确的结果为代价获得额外的速度,即您实际上并没有读取三个字节。跨度>
    猜你喜欢
    • 2023-03-08
    • 1970-01-01
    • 2018-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多