WPF应用中使用RJCP.IO.Ports打开串口后WaitMask请求循环问题
我是一名业余开发者,首次提问。目前正在使用WPF MVVM(.NET 6.0)开发店铺管理应用,通过RJCP.IO.Ports实现与收银机的串口通信,设备已能正常响应XON-XOFF协议命令。但用Device Monitor Studio监控端口时发现,打开串口后每秒会产生数百次WaitMask相关请求,循环不休。
请求视图的循环请求示例:
000041: I/O Request (DOWN): 2024-11-17 00:14:06,712104 +0,003315 IOCTL_SERIAL_SET_WAIT_MASK: Set wait mask · Wait Mask = SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR | SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING (0x1ff) 000042: I/O Request (UP): 2024-11-17 00:14:06,712203 +0,000099 IOCTL: IOCTL_SERIAL_SET_WAIT_MASK 000043: I/O Request (DOWN): 2024-11-17 00:14:06,712227 +0,000024 IOCTL_SERIAL_SET_WAIT_MASK: Set wait mask · Wait Mask = SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR | SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING (0x1ff) 000044: I/O Request (UP): 2024-11-17 00:14:06,712318 +0,000091 IOCTL: IOCTL_SERIAL_SET_WAIT_MASK 000045: I/O Request (DOWN): 2024-11-17 00:14:06,713507 +0,001189 IOCTL: IOCTL_SERIAL_GET_COMMSTATUS 000046: I/O Request (UP): 2024-11-17 00:14:06,713547 +0,000040 IOCTL_SERIAL_GET_COMMSTATUS: Retrieve COM status · Errors = 0x0 · Hold reasons = 0x0 · In queue = 0 · Out queue = 0 · EOF received = false · Wait for immediate = false 000047: I/O Request (DOWN): 2024-11-17 00:14:06,714582 +0,001035 IOCTL_SERIAL_WAIT_ON_MASK: Start waiting for event 000048: I/O Request (DOWN): 2024-11-17 00:14:06,717894 +0,003312 IOCTL_SERIAL_SET_WAIT_MASK: Set wait mask · Wait Mask = SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR | SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING (0x1ff) 000049: I/O Request (UP): 2024-11-17 00:14:06,718000 +0,000106 IOCTL_SERIAL_WAIT_ON_MASK: Wait for event · Wait Mask = 0x0 000050: I/O Request (UP): 2024-11-17 00:14:06,718037 +0,000037 IOCTL: IOCTL_SERIAL_SET_WAIT_MASK 000051: I/O Request (DOWN): 2024-11-17 00:14:06,718046 +0,000010 IOCTL: IOCTL_SERIAL_GET_COMMSTATUS 000052: I/O Request (UP): 2024-11-17 00:14:06,718093 +0,000046 IOCTL_SERIAL_GET_COMMSTATUS: Retrieve COM status · Errors = 0x0 · Hold reasons = 0x0 · In queue = 0 · Out queue = 0 · EOF received = false · Wait for immediate = false 000053: I/O Request (DOWN): 2024-11-17 00:14:06,720573 +0,002480 IOCTL_SERIAL_SET_WAIT_MASK: Set wait mask · Wait Mask = SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR | SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING (0x1ff) 000054: I/O Request (UP): 2024-11-17 00:14:06,720611 +0,000038 IOCTL: IOCTL_SERIAL_SET_WAIT_MASK 000055: I/O Request (DOWN): 2024-11-17 00:14:06,720643 +0,000032 IOCTL: IOCTL_SERIAL_GET_COMMSTATUS 000056: I/O Request (UP): 2024-11-17 00:14:06,720690 +0,000046 IOCTL_SERIAL_GET_COMMSTATUS: Retrieve COM status · Errors = 0x0 · Hold reasons = 0x0 · In queue = 0 · Out queue = 0 · EOF received = false · Wait for immediate = false 000057: I/O Request (DOWN): 2024-11-17 00:14:06,720710 +0,000021 IOCTL_SERIAL_WAIT_ON_MASK: Start waiting for event
我对串口工作机制不熟悉,尝试多种方法后未找到RJCP.IO.Ports库中设置WaitMask事件的明确方法。以下是我的服务代码:
using App.Contracts.Services; using App.ViewModels; using RJCP.IO.Ports; using System.IO; using System.Text; namespace App.Services; public class CashRegisterService : ICashRegisterService { private readonly Logger logSent; private readonly Logger logExc; private SerialPortStream P; public SerialPortStream Port => P; private readonly object portLock = new(); private bool isPortOpen; public CashRegisterService() { string defaultPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"); if (!Directory.Exists(defaultPath)) Directory.CreateDirectory(defaultPath); logSent = new Logger(defaultPath, ".log", "Rs232"); logExc = new Logger(defaultPath, ".exc", "Rs232Exc"); } public void SetPort(int portNum = 4, int baudRate = 9600, int databits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One) { if (portNum <= 0) throw new ArgumentException("Port number should be > 0."); P = new SerialPortStream { PortName = $"COM{portNum}", BaudRate = baudRate, DataBits = databits, Parity = parity, StopBits = stopBits, Handshake = Handshake.None }; P.DiscardNull = false; P.DiscardInBuffer(); P.DiscardOutBuffer(); } public void OpenConnection() { lock (portLock) { if (P == null) throw new InvalidOperationException("Port not found."); if (!isPortOpen) { try { P.Open(); P.WriteTimeout = 500; P.ReadTimeout = 500; P.DiscardInBuffer(); P.DiscardOutBuffer(); isPortOpen = true; WriteLog($"Connection opened {P.PortName} param: \"{P.BaudRate}, {P.DataBits}, {P.Parity}, {P.StopBits}, {P.Handshake}\""); } catch (Exception ex) { WriteLog($"Error: {ex.Message}", true); throw; } } } } public string ReadAnswer() { StringBuilder answer = new StringBuilder(); try { int bytesRead; byte[] buffer = new byte[256]; do { bytesRead = P.Read(buffer, 0, buffer.Length); answer.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead)); } while (P.BytesToRead > 0); } catch (TimeoutException) { WriteLog("Timeout: No answer."); } return answer.ToString(); } public void CloseConnection() { lock (portLock) { if (isPortOpen) { try { P.Close(); isPortOpen = false; WriteLog($"Connection {P.PortName} closed."); } catch (Exception ex) { WriteLog($"Error: {ex.Message}", true); } } } } public void SendCommand(string command) { lock (portLock) { if (!isPortOpen) throw new InvalidOperationException("Serial Port is closed."); try { P.Write(command); WriteLog($"command sent: {command}"); Thread.Sleep(100); try { string response = ReadAnswer(); if (!string.IsNullOrWhiteSpace(response)) WriteLog($"Datas received: {response}"); else WriteLog("Any data received."); } catch (TimeoutException) { WriteLog("Timeout: No answer."); } } catch (Exception ex) { WriteLog($"Error for command '{command}': {ex.Message}", true); throw; } } } private void WriteLog(string message, bool isError = false) { try { if (isError) logExc.writeLog(message); else logSent.writeLog(message); } catch (Exception ex) { Console.WriteLine($"Error Log: {ex.Message}"); } } //rest of the code }
请问是否遗漏了相关配置或方法,该如何解决此WaitMask请求循环问题?
根本原因:RJCP.IO.Ports的
SerialPortStream在使用同步Read/Write且设置了超时的情况下,内部会不断循环调用IOCTL_SERIAL_SET_WAIT_MASK和IOCTL_SERIAL_WAIT_ON_MASK来实现超时等待逻辑,这是库的默认行为,但过于频繁的调用会产生大量IO请求。方案1:调整超时时间
当前设置的ReadTimeout=500ms和WriteTimeout=500ms较短,会导致库更频繁触发等待循环。适当增大超时值(比如1000ms或更长),可减少循环频率。修改OpenConnection方法中的超时设置:P.WriteTimeout = 1000; P.ReadTimeout = 1000;方案2:改用异步IO模式
同步IO的超时机制是频繁WaitMask请求的核心,改用异步方法(ReadAsync/WriteAsync)可避免库内部的同步等待循环,大幅减少IO请求次数。修改ReadAnswer为异步版本:public async Task<string> ReadAnswerAsync() { StringBuilder answer = new StringBuilder(); try { byte[] buffer = new byte[256]; int bytesRead; do { bytesRead = await P.ReadAsync(buffer, 0, buffer.Length); answer.Append(Encoding.ASCII.GetString(buffer, 0, bytesRead)); } while (P.BytesToRead > 0); } catch (TimeoutException) { WriteLog("Timeout: No answer."); } return answer.ToString(); }同时修改
SendCommand为异步方法:public async Task SendCommandAsync(string command) { lock (portLock) { if (!isPortOpen) throw new InvalidOperationException("Serial Port is closed."); } try { await P.WriteAsync(Encoding.ASCII.GetBytes(command)); WriteLog($"command sent: {command}"); await Task.Delay(100); try { string response = await ReadAnswerAsync(); if (!string.IsNullOrWhiteSpace(response)) WriteLog($"Datas received: {response}"); else WriteLog("Any data received."); } catch (TimeoutException) { WriteLog("Timeout: No answer."); } } catch (Exception ex) { WriteLog($"Error for command '{command}': {ex.Message}", true); throw; } }注意:异步方法中不能在
lock块内执行异步操作,需将lock移到异步操作外,仅保护状态检查部分。方案3:自定义WaitMask设置(进阶)
RJCP.IO.Ports的SerialPortStream提供了SetWaitMask方法,可手动设置需要监听的事件掩码,减少不必要的事件监听,从而降低IO请求频率。在OpenConnection方法中添加:// 仅监听必要事件,比如接收字符和错误事件 P.SetWaitMask(RJCP.IO.Ports.SerialEvent.RxChar | RJCP.IO.Ports.SerialEvent.Error);这样库只会关注指定的事件,减少不必要的WaitMask设置请求。
内容的提问来源于stack exchange,提问作者TytyNoobCode

