[英]Awaiting data from Serial Port in C#
我有一个使用RS-232从无线电接收数据的应用程序。 这些无线电使用API与多个客户端进行通信。 为了使用无线电,我创建了一个与它们进行通信的库,其他软件可以利用这些库与常规SerialPort连接进行最小的更改。 该库从SerialPort对象读取,然后根据接收到的无线电将输入的数据插入不同的缓冲区。 每个接收到的数据包都包含一个标头,指示其长度,源等。
我首先从端口读取固定长度的标头并进行解析。 在标头中,数据的长度是在数据有效载荷本身之前定义的,因此一旦我知道了数据的长度,便会等待那么多的数据可用,然后读入那么多的字节。
示例(省略标题中的其他元素):
// Read header
byte[] header = new byte[RCV_HEADER_LENGTH];
this.Port.Read(header, 0, RCV_HEADER_LENGTH);
// Get length of data in packet
short dataLength = header[1];
byte[] payload = new byte[dataLength];
// Make sure all the payload of this packet is ready to read
while (this.Port.BytesToRead < dataLength) { }
this.Port.Read(payload, 0, dataLength);
显然,空端口是坏的。 如果由于某种原因数据永不到达,线程将锁定。 我还没有遇到这个问题,但是我正在寻找一种优雅的方式来做到这一点。 我的第一个想法是添加一个简短的计时器,该计时器从while循环之前开始,并在其终止时设置abortRead标志,这会中断while循环,如下所示:
// Make sure all the payload of this packet is ready to read
abortRead = false;
readTimer.Start();
while (this.Port.BytesToRead < dataLength && !abortRead) {}
此代码需要尽快处理恒定的传入数据流,因此将开销降至最低是一个问题,并且想知道我是否做得正确。
如果您想真正解决此问题,则需要在后台运行代码。 有不同的选择可以做到这一点。 您可以启动线程,启动Task
或使用async await
。
要完全涵盖所有选项,答案将是无穷无尽的。 如果您使用带有默认调度程序的线程或任务,并且等待时间预计会很短,则可以使用SpinWait.SpinUntil
而不是while循环。 这将比您的解决方案更好地执行:
SpinWait.SpinUntil(() => this.Port.BytesToRead >= dataLength);
如果您可以随意使用async await
,那么我建议您使用此解决方案,因为您只需对代码进行一些更改即可。 您可以使用Task.Delay
,在最佳情况下,您可以传递CancellationToken
来取消操作:
try {
while (this.Port.BytesToRead < dataLength) {
await Task.Delay(100, cancellationToken);
}
}
catch(OperationCancelledException) {
//Cancellation logic
}
您不必运行while循环, Read
方法将为您填充缓冲区,或者如果在SerialPort.ReadTimeout
时间(您可以根据需要进行调整)内未填充缓冲区,则将抛出TimeoutException
。
但是有一些一般性的说明-while循环将导致大量的CPU工作,而在数毫秒之内将需要花费数据才能到达,而while循环迭代将有大量的这种情况,您应该在其中添加一些Thread.Sleep
。
我想我可以通过SerialPort
DataReceived
事件来异步执行此操作。
// Class fields
private const int RCV_HEADER_LENGTH = 8;
private const int MAX_DATA_LENGTH = 255;
private SerialPort Port;
private byte[] PacketBuffer = new byte[RCV_HEADER_LENGTH + MAX_DATA_LENGTH];
private int Readi = 0;
private int DataLength = 0;
// In your constructor
this.Port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
if (e.EventType != SerialData.Chars)
{
return;
}
// Read all available bytes.
int len = Port.BytesToRead;
byte[] data = new byte[len];
Port.Read(data, 0, len);
// Go through each byte.
for (int i = 0; i < len; i++)
{
// Add the next byte to the packet buffer.
PacketBuffer[Readi++] = data[i];
// Check if we've received the complete header.
if (Readi == RCV_HEADER_LENGTH)
{
DataLength = PacketBuffer[1];
}
// Check if we've received the complete data.
if (Readi == RCV_HEADER_LENGTH + DataLength)
{
// The packet is complete add it to the appropriate buffer.
Readi = 0;
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.