【问题标题】:C# Async Serial Port ReadC# 异步串口读取
【发布时间】:2014-06-04 15:20:23
【问题描述】:

我有一个类,它使用 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 事件处理程序中的另一个事件处理程序是否合适?

【问题讨论】:

    标签: c# asynchronous serial-port blocking


    【解决方案1】:

    使用异步编程(不要忘记首先将您的应用程序定位到 .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);
       }
    }
    

    【讨论】:

    • FWI:我不得不使用System.IO.Ports.SerialPort serialPort 而不仅仅是SerialPort serialPort。不知道我是否搞砸了什么,但它只想那样工作。是的,我是using System.IO.Ports。 --- 但是,我仍然无法让它读取任何内容:(
    • 但我发现ReadTimeout 不适用于BaseStream
    • 添加广告超时的方法如下:stackoverflow.com/a/41193744/3717478
    【解决方案2】:

    这会消耗 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。

    【讨论】:

    • 汉斯,非常感谢您的回复。如果最初收到的字节数少于 5 个,它似乎会停止,我需要弄清楚如何再次“循环”回来。例如,我总是读取 20 个字节的数组。串行端口通常一次拉入所有 20 个字节,但有时它会拉入 1 个字节,然后是 19 个字节。在这种情况下,上面的代码读取 1 个字节,然后停止。我是否需要在某个地方睡觉才能让其余数据有时间进入?
    • DataReceived 事件应在其余字节到达时再次触发。你永远不应该做类似“再次循环”之类的事情。我不知道为什么这当然不会发生。一些您实际读取字节但未实际使用它们的错误很常见。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多