C#中如何等待kernel32 ReadFile()或设置超时,解决打印机响应异常?
解决打印机响应滞留与旧响应干扰问题
针对你遇到的打印机响应捕获异常、新命令数据滞留队列的问题,核心解决思路是在发送新命令前彻底清空设备缓冲区的所有待处理响应,同时优化读写逻辑避免阻塞。以下是具体实现方案:
关键优化点
- 发送新命令前,循环读取设备缓冲区直到无数据可读,确保旧响应被完全清理
- 使用带超时的读取逻辑,避免
ReadFile无限阻塞 - 并发场景下添加锁机制,保证命令发送与响应读取的原子性,防止交叉干扰
修改后的代码实现
private object _lockObj = new object(); // 并发场景下的锁对象 public Dictionary<string, string> ReadMany(List<string> settings) { lock (_lockObj) // 加锁确保同一时间只有一个命令在处理 { try { // 第一步:清空缓冲区所有待处理响应 FlushDeviceBuffer(); var data = CreateReadManySettingXml(settings); if (Handle.IsInvalid) { throw new Exception("无法打开设备句柄"); } // 发送新命令 uint bytesWritten = 0; bool success = WriteFile(Handle, data, (uint)data.Length, out bytesWritten, IntPtr.Zero); if (!success || bytesWritten != data.Length) { throw new Exception("写入设备失败"); } // 读取新命令的响应(带超时处理) byte[] readBuffer = new byte[10000]; uint bytesRead = 0; success = ReadWithTimeout(Handle, readBuffer, 10000, out bytesRead, 500); // 500ms超时 if (!success) { throw new Exception("读取USB设备数据失败"); } Array.Resize(ref readBuffer, (int)bytesRead); return ReadManyValues(Encoding.UTF8.GetString(readBuffer)); } catch (Exception e) { throw new Exception(e.Message); } } } /// <summary> /// 清空设备缓冲区的所有待处理数据 /// </summary> private void FlushDeviceBuffer() { byte[] buffer = new byte[10000]; uint bytesRead; // 循环读取直到无数据可读(ReadFile返回false且错误码为ERROR_NO_DATA) while (true) { bool success = ReadFile(Handle, buffer, 10000, out bytesRead, IntPtr.Zero); if (!success) { int errorCode = Marshal.GetLastWin32Error(); // 没有更多数据时退出循环 if (errorCode == 232) // ERROR_NO_DATA { break; } // 其他错误直接抛出 throw new Exception($"清空缓冲区失败,错误码:{errorCode}"); } // 如果读取到0字节也退出 if (bytesRead == 0) { break; } } } /// <summary> /// 带超时的ReadFile封装 /// </summary> private bool ReadWithTimeout(SafeFileHandle handle, byte[] buffer, uint bufferSize, out uint bytesRead, int timeoutMs) { using (var evt = new ManualResetEvent(false)) { var overlapped = new NativeOverlapped(); overlapped.EventHandle = evt.SafeWaitHandle.DangerousGetHandle(); bool success = ReadFile(handle, buffer, bufferSize, out bytesRead, ref overlapped); if (!success) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != 997) // ERROR_IO_PENDING { return false; } // 等待超时或操作完成 if (!evt.WaitOne(timeoutMs)) { // 超时取消操作 CancelIo(handle); bytesRead = 0; return false; } // 获取实际读取的字节数 success = GetOverlappedResult(handle, ref overlapped, out bytesRead, false); } return success; } } // 需要引入的Win32API声明 [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CancelIo(SafeFileHandle hFile); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetOverlappedResult(SafeFileHandle hFile, ref NativeOverlapped lpOverlapped, out uint lpNumberOfBytesTransferred, bool bWait);
关于await卡住的问题说明
直接给同步版ReadFile加await会卡住,是因为同步ReadFile是阻塞式调用,没有使用**重叠IO(Overlapped I/O)**机制。要实现异步读取,必须配合OVERLAPPED结构体和事件通知,就像上面ReadWithTimeout方法中那样,通过等待事件完成来实现异步非阻塞的读取,避免线程无限挂起。
内容的提问来源于stack exchange,提问作者LeNiU
相关产品推荐
相关产品推荐

