C#串口环路在两个线程同步问题

C# serial port loop in two thread sync problem

我是 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;
       }
   }

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

也就是说……

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

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

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

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 状态变量和所有来回线程的东西在您的示例中对我来说似乎都没有用。这只会让事情变得复杂,更容易引入错误,而不会增加任何有益的东西。所以最好的解决方案就是完全省略所有这些。 :)