编辑
2025-10-11
C#
00

目录

关键设计理念
异步编程
主要特性
代码解析
类定义与构造函数
异步连接方法
异步数据读取
数据发送方法
使用示例
完整代码
注意事项
结论

在嵌入式系统、物联网(IoT)和工业通讯领域,串口通讯仍然是一种广泛使用的通信方式。本文将深入探讨如何使用C#实现一个健壮、高效的异步串口通讯管理器。

关键设计理念

异步编程

异步编程是现代.NET应用程序的重要特性,它允许:

  • 非阻塞的I/O操作
  • 提高应用程序的响应性
  • 有效利用系统资源

主要特性

我们的串口管理器将具备以下特性:

  • 异步连接和读取
  • 错误处理
  • 灵活的数据接收机制
  • 资源释放管理

代码解析

类定义与构造函数

C#
public class SerialPortManager : IDisposable { private SerialPort _serialPort; private readonly string _portName; private readonly int _baudRate; private CancellationTokenSource _cancellationTokenSource; // 数据接收事件 public event EventHandler<byte[]> DataReceived; public SerialPortManager(string portName, int baudRate) { _portName = portName; _baudRate = baudRate; _cancellationTokenSource = new CancellationTokenSource(); } }

异步连接方法

C#
public async Task StartAsync() { try { await ConnectSerialPortAsync(); } catch (Exception ex) { LogError($"启动失败:{ex.Message}"); throw; } } private async Task ConnectSerialPortAsync() { _serialPort = new SerialPort(_portName, _baudRate) { ReadTimeout = 500, WriteTimeout = 500, ReadBufferSize = 4096, WriteBufferSize = 4096 }; _serialPort.Open(); _ = StartAsyncReading(); }

异步数据读取

C#
private async Task StartAsyncReading() { byte[] buffer = new byte[4096]; while (_serialPort.IsOpen && !_cancellationTokenSource.Token.IsCancellationRequested) { int bytesToRead = _serialPort.BytesToRead; if (bytesToRead > 0) { int readBytes = await _serialPort.BaseStream.ReadAsync( buffer, 0, bytesToRead, _cancellationTokenSource.Token); ProcessReceivedData(buffer.Take(readBytes).ToArray()); } await Task.Delay(50, _cancellationTokenSource.Token); } }

数据发送方法

C#
public async Task<bool> SendDataAsync(byte[] data) { if (_serialPort?.IsOpen == true) { await _serialPort.BaseStream.WriteAsync( data, 0, data.Length, _cancellationTokenSource.Token); return true; } return false; }

使用示例

C#
public async Task ExampleUsage() { var serialManager = new SerialPortManager("COM3", 9600); // 订阅数据接收事件 serialManager.DataReceived += (sender, data) => { Console.WriteLine($"接收到数据: {BitConverter.ToString(data)}"); }; // 启动串口 await serialManager.StartAsync(); // 发送数据 byte[] dataToSend = new byte[] { 0x01, 0x02, 0x03 }; await serialManager.SendDataAsync(dataToSend); }

完整代码

C#
using System; using System.IO.Ports; using System.Threading; using System.Threading.Tasks; namespace AppSerialPortTry { /// <summary> /// 串口管理器 /// </summary> public class SerialPortManager : IDisposable { private SerialPort _serialPort; private readonly string _portName; private readonly int _baudRate; private CancellationTokenSource _cancellationTokenSource; // 数据接收事件 public event EventHandler<byte[]> DataReceived; public SerialPortManager(string portName, int baudRate) { _portName = portName; _baudRate = baudRate; _cancellationTokenSource = new CancellationTokenSource(); } /// <summary> /// 异步启动串口通信 /// </summary> public async Task StartAsync() { try { await ConnectSerialPortAsync(); } catch (Exception ex) { LogError($"启动失败:{ex.Message}"); throw; } } /// <summary> /// 连接串口 /// </summary> private async Task ConnectSerialPortAsync() { try { // 创建串口实例 _serialPort = new SerialPort(_portName, _baudRate) { ReadTimeout = 500, WriteTimeout = 500, ReadBufferSize = 4096, WriteBufferSize = 4096 }; // 配置串口事件 _serialPort.DataReceived += SerialPort_DataReceived; _serialPort.ErrorReceived += SerialPort_ErrorReceived; // 打开串口 _serialPort.Open(); LogInfo($"串口 {_portName} 连接成功"); // 启动异步读取 _ = StartAsyncReading(); } catch (Exception ex) { LogError($"连接串口失败:{ex.Message}"); throw; } } /// <summary> /// 异步读取数据 /// </summary> private async Task StartAsyncReading() { try { byte[] buffer = new byte[4096]; while (_serialPort != null && _serialPort.IsOpen && !_cancellationTokenSource.Token.IsCancellationRequested) { int bytesToRead = _serialPort.BytesToRead; if (bytesToRead > 0) { bytesToRead = Math.Min(bytesToRead, buffer.Length); int readBytes = await _serialPort.BaseStream.ReadAsync(buffer, 0, bytesToRead, _cancellationTokenSource.Token); if (readBytes > 0) { byte[] receivedData = new byte[readBytes]; Array.Copy(buffer, receivedData, readBytes); ProcessReceivedData(receivedData); } } await Task.Delay(50, _cancellationTokenSource.Token); } } catch (OperationCanceledException) { // 正常取消 } catch (Exception ex) { LogError($"异步读取数据错误:{ex.Message}"); } } /// <summary> /// 处理接收的数据 /// </summary> private void ProcessReceivedData(byte[] data) { string hexData = BitConverter.ToString(data); LogInfo($"接收到数据:{hexData}"); // 触发数据接收事件 DataReceived?.Invoke(this, data); } /// <summary> /// 串口错误事件 /// </summary> private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e) { LogError($"串口错误:{e.EventType}"); } /// <summary> /// 数据接收事件 /// </summary> private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { // 根据不同的接收模式处理 switch (e.EventType) { case SerialData.Chars: // 有字符可读 int bytesToRead = _serialPort.BytesToRead; if (bytesToRead > 0) { byte[] buffer = new byte[bytesToRead]; _serialPort.Read(buffer, 0, bytesToRead); // 可以触发一个事件或进行进一步处理 ProcessReceivedData(buffer); } break; case SerialData.Eof: LogWarning("串口接收到文件结束符"); break; default: LogInfo($"接收到未处理的事件类型: {e.EventType}"); break; } } catch (Exception ex) { LogError($"串口数据接收事件处理错误:{ex.Message}"); } } /// <summary> /// 发送数据 /// </summary> public async Task<bool> SendDataAsync(byte[] data) { try { if (_serialPort != null && _serialPort.IsOpen) { await _serialPort.BaseStream.WriteAsync(data, 0, data.Length, _cancellationTokenSource.Token); return true; } return false; } catch (Exception ex) { LogError($"发送数据失败:{ex.Message}"); return false; } } /// <summary> /// 关闭串口 /// </summary> public void Close() { _cancellationTokenSource.Cancel(); if (_serialPort != null) { try { if (_serialPort.IsOpen) { _serialPort.Close(); } // 取消事件绑定 _serialPort.DataReceived -= SerialPort_DataReceived; _serialPort.ErrorReceived -= SerialPort_ErrorReceived; } catch (Exception ex) { LogError($"关闭串口时发生错误:{ex.Message}"); } } } /// <summary> /// 实现 IDisposable /// </summary> public void Dispose() { Close(); _cancellationTokenSource?.Dispose(); _serialPort?.Dispose(); } #region 日志方法 private void LogInfo(string message) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"[INFO] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}"); Console.ResetColor(); } private void LogWarning(string message) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"[WARNING] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}"); Console.ResetColor(); } private void LogError(string message) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"[ERROR] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}"); Console.ResetColor(); } #endregion } }
C#
using System.IO.Ports; namespace AppSerialPortTry { internal class Program { static void Main(string[] args) { Console.WriteLine("串口通信监控演示程序"); RunSerialPortMonitorAsync().Wait(); Console.ReadKey(); } static async Task RunSerialPortMonitorAsync() { var serialPortManager = new SerialPortManager("COM1", 9600); await serialPortManager.StartAsync(); await serialPortManager.SendDataAsync(System.Text.Encoding.Default.GetBytes(" Hello World!")); serialPortManager.DataReceived += (sender, e) => { Console.WriteLine(System.Text.Encoding.Default.GetString(e)); }; } } }

image.png

注意事项

  • 50ms的延迟可以根据实际应用场景调整
  • 4096字节的缓冲区大小适用于大多数中小型通讯场景
  • 对于高频率、大数据量的通讯,可能需要进一步优化

结论

通过使用异步编程模式,我们创建了一个既灵活又高效的串口通讯管理器。这种实现方式不仅提高了代码的可读性,还显著改善了应用程序的性能和响应能力。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!