必须先用 SerialPort.GetPortNames() 检查端口是否存在再 Open(),否则易因端口不存在抛出 IOException;读数据需设 ReadTimeout 并避免 ReadLine(),改用轮询+缓冲区状态机;写前须配置 DTR/RTS 并延时;关闭时须 Close() 后 Dispose() 或用 using 确保句柄释放。

SerialPort 构造和 Open() 之前必须检查端口名是否存在
直接 new SerialPort("COM99") 再 Open(),十有八九抛出 UnauthorizedAccessException 或 IOException——不是权限问题,而是端口压根不存在。Windows 不会提前校验 COMx 名字是否真实挂载了设备。
- 用
SerialPort.GetPortNames()先获取当前可用列表,再比对用户输入或配置项;没匹配就别往下走 - 特别注意虚拟串口(如 CH340、CP2102 驱动创建的 COM)可能延迟出现,热插拔后需重新调用
GetPortNames() - 某些工控场景下,
COM1被 BIOS 禁用或被其他服务独占,Open()仍会失败,此时要捕获IOException并提示“端口被占用”
读数据不能只靠 ReadLine(),得处理不完整帧和超时
ReadLine() 看着省事,但 RS232 通信里几乎没有标准换行符。硬件发来的数据可能是不定长二进制帧,中间夹着 \0 或无结束符,ReadLine() 会一直阻塞到超时(默认无穷等待),卡死线程。
- 改用
ReadByte()或Read(byte[], int, int)配合BytesToRead轮询,更可控 - 必须设
ReadTimeout(比如 500ms),否则单次读卡住,整个接收逻辑就僵住 - 若协议有帧头(如 0xAA 0x55),建议用缓冲区累积 + 状态机解析,别依赖
ReadExisting()拼字符串——它会丢掉未解码的字节
写数据后不加 Delay 或没等 DTR/RTS 就发,硬件收不到
很多老式仪表、PLC 要求串口线 DTR 或 RTS 信号拉高才开始接收。.NET 的 SerialPort 默认不操作这些控制线,直接 Write() 后立刻发数据,硬件还没“醒”。
- 发数据前手动置位:
port.DtrEnable = true;或port.RtsEnable = true;(查清设备手册要哪个) - 部分设备需要 10–100ms 延迟让硬件稳定,
Thread.Sleep(50)比空循环靠谱,但别在 UI 线程里 Sleep - 如果用
WriteAsync(),记得 await 完再切控制线,否则顺序错乱
关闭串口时没 Dispose() 或没先 Close(),下次 Open() 报错
常见错误是只调 Close() 就完事,或者对象被 GC 回收前没显式释放资源。Windows 下串口句柄没彻底释放,再 new 同一个 COM 名称就会报 IOException: The port is already open.——其实根本没开,是句柄残留。
- 务必按顺序:先
Close(),再Dispose();或者直接用using (var port = new SerialPort(...)) { ... } - 如果串口对象是类成员变量,
Dispose()必须在窗体FormClosing或服务停止时调用,不能靠析构函数 - 调试时可查句柄数:任务管理器 → 性能 → 打开资源监视器 → 查看进程的 HANDLE 数,反复开关串口后不降,基本就是没 Dispose
串口最麻烦的从来不是读写函数本身,而是硬件握手时机、缓冲区边界、句柄生命周期这三块——它们不出错时一切正常,一出就是“明明代码没变,换个电脑就不行”。










