You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:18:09