c#串口环路中两个线程同步问题



我是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发送的数据,并且在发送线程有机会将sinh设置为2之前引发DataReceived事件,则事件处理程序将忽略接收到的数据。

其次,您的代码也无法检查从端口读取的字节数。这可能导致您在尝试处理它之前无法实际读取三个字节的完整响应,因为您可能在所有三个字节都可以读取之前获得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,那么async版本更可取。但是,非异步版本可以很好地作为您现在所拥有的插件的替代品。

在任何一种情况下,sinh状态变量和所有来回线程的东西在你的例子中似乎对我没有用处。它只是使事情复杂化,使引入bug变得更容易,而没有添加任何有益的东西。所以最好的办法就是把这些都省略掉。:)

最新更新