COM端口异步读取返回ERROR_IO_PENDING问题及代码求助
Windows重叠IO串口读取问题排查与解答
问题场景
需要从COM端口获取数据帧,每帧258字节,每秒约8.5帧,帧以/开头、?结尾。编写Windows平台重叠IO串口代码后,出现以下问题:
WaitForSingleObject()返回WAIT_OBJECT_0,但ReadFile始终返回false,GetLastError()报告ERROR_IO_PENDING- 打印缓冲区数据存在乱序、不一致的情况
代码示例
#include <iostream> #include <time.h> #include <string> #include "windows.h" #pragma comment (lib, "OneCore.lib") int main() { int portnum = 0; ULONG ptNums[300]; ULONG some = 300; ULONG ptsFound[20]; LPCSTR fName = "COM4"; ULONG status = GetCommPorts(ptNums, 100, ptsFound); if (status == ERROR_SUCCESS) { std::cout << "ports found:" << ptsFound[0] << "\n"; for (int i = 0; i < ptsFound[0]; i++) { portnum = ptNums[i]; std::cout << "COM" << portnum << "\n"; } } HANDLE port = CreateFileA(fName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if (port == INVALID_HANDLE_VALUE) { std::cout << "unable to open port\n"; } DCB dcb; dcb = { 0 }; //this is the default, use 8N1 dcb.DCBlength = sizeof(DCB); dcb.fBinary = TRUE; dcb.BaudRate = 250000; dcb.fParity = FALSE; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; DWORD eventMask; SetCommState(port, &dcb); SetCommMask(port,EV_TXEMPTY|EV_RXCHAR); bool exitloop = false; char arduBuf[258]; OVERLAPPED ov; ov.Offset = 0; ov.OffsetHigh = 0; ov.hEvent = CreateEvent(0, TRUE, 0, 0); SetupComm(port, 300, 300); while (!exitloop) { GetCommMask(port, &eventMask); BOOL dataRet = WaitCommEvent(port, &eventMask, &ov); DWORD dwait = WaitForSingleObject(ov.hEvent, INFINITE); if (eventMask & EV_TXEMPTY) { std::cout << "all data was sent\n"; exitloop = true; } if (dwait == WAIT_OBJECT_0) { GetCommMask(port, &eventMask); std::cout << "some random stuff was received\n"; BOOL status = ReadFile(port, arduBuf, sizeof(arduBuf), NULL, &ov); std::cout << "status:" << status << "\nerror is:" << GetLastError() << "\n"; PurgeComm(port, PURGE_RXCLEAR); if (status) { std::cout << arduBuf << "\n"; } ResetEvent(ov.hEvent); //exitloop = true; } if (dwait == WAIT_TIMEOUT) { std::cout << "timeout\n"; } } }
疑问列表
- 代码明显存在问题,能否指出问题所在?
- 将读取逻辑放在while循环中是否合适?是否会引发问题?
WaitForSingleObject()行提示ov.hEvent可能为0,原因是什么?是否会造成影响?- 何时需要调用
ResetEvent重置事件?
解答
1. 代码核心问题
- 重叠IO对象复用冲突:同一个
OVERLAPPED结构体同时用于WaitCommEvent和ReadFile操作,导致事件信号状态混乱,IO操作的完成通知互相干扰。 - 未处理
ERROR_IO_PENDING:ReadFile返回false且错误码为ERROR_IO_PENDING是重叠IO的正常状态,表示操作尚未完成,此时需调用GetOverlappedResult等待操作完成并获取实际读取字节数,而非直接忽略或清理缓冲区。 - 错误使用
PurgeComm:ReadFile后立即调用PURGE_RXCLEAR会清空接收缓冲区,导致未读取数据丢失,这是数据乱序、不一致的关键原因之一。 WaitCommEvent逻辑缺失:调用WaitCommEvent后,若返回false且错误码为ERROR_IO_PENDING才需要等待事件;若直接返回true说明事件已触发,无需再调用WaitForSingleObject,当前代码未区分这两种情况。- 串口参数与缓冲区问题:未校验
SetCommState返回值,无法确认参数是否设置成功;SetupComm设置的300字节接收缓冲区过小,每秒8.5帧的速率(约2193字节/秒)易导致丢帧。
2. 读取逻辑放在while循环的合理性
放在while循环本身是合适的,串口读取是持续过程,循环能保证持续监听和处理数据。但当前循环逻辑存在问题:
- 每次循环重新调用
WaitCommEvent,未正确处理前一次IO操作状态,易导致事件信号堆积。 - 循环内的
EV_TXEMPTY判断会错误终止循环,该事件是发送缓冲区空的通知,与接收逻辑无关,当前场景无需监听。
3. ov.hEvent可能为0的原因及影响
- 原因:
CreateEvent调用可能失败(如系统资源不足),此时返回句柄为NULL(即0),当前代码未检查该函数返回值,存在风险。 - 影响:若
ov.hEvent为0,WaitForSingleObject传入无效句柄,函数会返回WAIT_FAILED,后续逻辑完全混乱,无法正确监听IO事件。
4. ResetEvent的调用时机
- 手动重置事件(
CreateEvent第二个参数为TRUE)触发后会保持信号状态,需调用ResetEvent重置为无信号。 - 正确时机:在发起新的重叠IO操作之前调用
ResetEvent,确保事件处于无信号状态,后续等待操作才能正确捕获IO完成的信号。比如每次调用WaitCommEvent或ReadFile前重置事件。 - 当前代码在
ReadFile后重置事件是错误的,应在下次使用该OVERLAPPED对象发起新操作前重置。
内容的提问来源于stack exchange,提问作者Vroom ZOOM
相关产品推荐
相关产品推荐

