C#异步串口读取

13

我有一个使用C#的DataReceived事件处理程序从串口读取数据的类。当我接收到数据时,我知道头部将有5个字节,因此在获得至少这些字节之前,我不想对数据做任何操作。我的当前代码如下:

while (serialPort.BytesToRead<5)
{
//Do nothing while we have less bytes than the header size
}

//Once at least 5 bytes are received, process header

据我所了解,这段代码是阻塞的,并且需要改进。我正在寻求有关如何改进的建议。在DataReceived事件处理程序内添加另一个事件处理程序是否合适?

2个回答

21

使用异步编程(不要忘记首先将您的应用程序目标设为.NET Framework 4.5)。

这里是我对SerialPort的扩展方法的实现。

using System;
using System.IO.Ports;
using System.Threading.Tasks;

namespace ExtensionMethods.SerialPort
{
    public static class SerialPortExtensions
    {
        public async static Task ReadAsync(this SerialPort serialPort, byte[] buffer, int offset, int count)
        {
            var bytesToRead = count;
            var temp = new byte[count];

            while (bytesToRead > 0)
            {
                var readBytes = await serialPort.BaseStream.ReadAsync(temp, 0, bytesToRead);
                Array.Copy(temp, 0, buffer, offset + count - bytesToRead, readBytes);
                bytesToRead -= readBytes;
            }
        }

        public async static Task<byte[]> ReadAsync(this SerialPort serialPort, int count)
        {
            var buffer = new byte[count];
            await serialPort.ReadAsync(buffer, 0, count);
            return buffer;
        }
    }
}

这里是如何阅读的方法:

public async void Work()
{
   try
   {
       var data = await serialPort.ReadAsync(5);
       DoStuff(data);
   }
   catch(Exception excepcion)
   {
       Trace.WriteLine(exception.Message);
   }
}

注意:我不得不使用 System.IO.Ports.SerialPort serialPort 而不是仅仅使用 SerialPort serialPort。不知道是否出了什么问题,但只有这种方式才能工作。是的,我已经 using System.IO.Ports。--- 但是,我仍然无法读取任何东西 :( - MagicLegend
3
但我发现 BaseStream 上的 ReadTimeout 不起作用。 - Felix
1
这里是如何添加广告超时时间的方法:https://dev59.com/mWUp5IYBdhLWcg3wbnQS#41193744 - user3717478

3

那会烧掉100%的核心,你不想这样做。正确的方法是让程序在Read()调用上阻塞。你可以像这样编写:

private byte[] rcveBuffer = new byte[MaximumMessageSize];
private int rcveLength;

void ReceiveHeader() {
    while (rcveLength < 5) {
        rcveLength += serialPort.Read(rcveBuffer, rcveLength, 5 - rcveLength);
    }
}

或者如果您使用DataReceived事件,则可以像这样:

    private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) {
        if (e.EventType != System.IO.Ports.SerialData.Chars) return;
        if (rcveLength < 5) {
            rcveLength += serialPort.Read(rcveBuffer, rcveLength, 5 - rcveLength);
        }
        if (rcveLength >= 5) {
            // Got the header, read the rest...
        }
    }

在获取并处理完整个消息后,不要忘记将rcveLength设置回0。


Hans,非常感谢你的回复。如果刚开始接收到的字节数少于5个,它似乎会停止,我需要想办法再次“循环”。例如,我总是在读取包含20个字节的数组。串口通常会一次性拉入所有20个字节,但有时它会拉入1个字节,然后是19个字节。在这种情况下,上面的代码读取1个字节,然后停止。我需要在某个地方加入等待时间,让其余数据有足够时间进来吗? - mikeminer
当其余的字节到达时,DataReceived事件应该再次触发。你永远不应该做任何像“再次回路”这样的事情。当然,我不知道为什么这种情况不会发生。实际上读取字节但实际上不使用它们的一些错误是很常见的。 - Hans Passant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接