共计 2016 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
在 C# 串口通信开发中,很多开发者习惯使用 Thread.Sleep(500) 这种简单粗暴的方式来等待设备响应。虽然这种方法实现起来简单,但它存在几个严重的问题:

- 线程阻塞:调用线程会被完全挂起,无法执行其他任务
- 资源浪费:CPU 时间片被白白浪费在无意义的等待上
- 响应延迟:固定的等待时间无法适应不同设备的响应速度
- 可伸缩性差:难以应对需要同时管理多个串口连接的情况
技术方案对比
在串口通信中,我们主要有三种处理等待的策略:
- 同步阻塞(Thread.Sleep)
- 优点:实现简单
-
缺点:如前所述的各种问题
-
异步回调(事件驱动)
- 使用 SerialPort 的 DataReceived 事件
- 优点:非阻塞
-
缺点:代码逻辑分散,错误处理复杂
-
async/await 模式
- 优点:非阻塞、代码线性、易于维护
- 缺点:需要.NET 4.5+ 支持
核心实现
以下是使用 async/await 实现非阻塞串口通信的完整示例:
public class AsyncSerialPort : IDisposable
{
private readonly SerialPort _serialPort;
private readonly CancellationTokenSource _cts;
public AsyncSerialPort(string portName, int baudRate)
{_serialPort = new SerialPort(portName, baudRate);
_cts = new CancellationTokenSource();}
/// <summary>
/// 异步读取串口数据
/// </summary>
/// <param name="timeoutMs"> 超时时间(毫秒)</param>
/// <returns> 读取到的数据 </returns>
public async Task<byte[]> ReadAsync(int timeoutMs)
{using (var timeoutCts = new CancellationTokenSource(timeoutMs))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, timeoutCts.Token))
{
try
{var buffer = new byte[1024];
var stream = _serialPort.BaseStream;
_serialPort.Open();
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, linkedCts.Token);
if (bytesRead > 0)
{var result = new byte[bytesRead];
Array.Copy(buffer, result, bytesRead);
return result;
}
return Array.Empty<byte>();}
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested)
{throw new TimeoutException("读取操作超时");
}
catch (Exception ex)
{
// 记录日志等操作
throw;
}
}
}
public void Dispose()
{_cts.Cancel();
_serialPort?.Dispose();
_cts?.Dispose();}
}
性能考量
我们通过基准测试对比了三种方案在以下指标上的表现:
- CPU 占用率
- Thread.Sleep: 接近 0%(因为线程被挂起)
-
异步方案: 约 2 -5%(但可以同时处理其他任务)
-
响应时间
- Thread.Sleep: 固定延迟(即使数据提前到达)
-
异步方案: 数据到达立即响应
-
吞吐量
- Thread.Sleep: 受限于固定延迟
- 异步方案: 可充分利用带宽
避坑指南
- 串口缓冲区溢出
- 设置合适的 Read/WriteTimeout
- 及时处理接收到的数据
-
调整缓冲区大小
-
跨线程 UI 更新
- 使用 Dispatcher.Invoke (WPF)
- 使用 Control.Invoke (WinForms)
-
避免直接访问 UI 控件
-
异常处理
- 区分 OperationCanceledException 和普通异常
- 确保资源释放
- 记录详细的错误日志
总结与延伸
async/await 模式不仅适用于串口通信,还可以推广到其他 I / O 密集型场景,如:
- 网络通信
- 文件操作
- 数据库访问
通过采用这种现代化的异步编程模型,我们可以构建出响应更快、资源利用率更高的应用程序。建议开发者尽早摆脱 Thread.Sleep 这种落后的编程习惯,拥抱异步编程的诸多优势。
在实际项目中,还需要考虑:
- 不同.NET 版本对异步的支持差异
- 与现有同步代码的兼容性
- 团队成员的异步编程经验
随着.NET 生态的不断发展,异步编程已经成为高效系统开发的必备技能,值得每个 C# 开发者深入掌握。
正文完
