You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C#中Readfile()非阻塞替代及HID设备状态检测技术问询

解决Windows C# HID设备读取卡顿与状态检测问题

针对你遇到的HID设备未开机时ReadFile()阻塞卡顿、以及需要检测设备开关机状态的问题,我整理了几个实用的解决方案:

一、调用ReadFile()前判断是否有数据可读

Windows API提供了PeekNamedPipe函数,专门用于在不读取数据的前提下,检查管道(包括HID设备的文件句柄)中是否有可用数据,完全是非阻塞的,不会导致程序卡顿。

你可以在C#中通过P/Invoke来调用它,示例代码如下:

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool PeekNamedPipe(IntPtr hNamedPipe, IntPtr lpBuffer, uint nBufferSize, out uint lpBytesRead, out uint lpTotalBytesAvail, out uint lpBytesLeftThisMessage);

// 使用示例
public bool HasDataAvailable(IntPtr hidDeviceHandle)
{
    uint bytesRead;
    uint totalBytesAvail;
    uint bytesLeft;
    
    // 传入空缓冲区,只查询可用数据量
    bool result = PeekNamedPipe(hidDeviceHandle, IntPtr.Zero, 0, out bytesRead, out totalBytesAvail, out bytesLeft);
    
    if (result && totalBytesAvail > 0)
    {
        return true;
    }
    return false;
}

在调用ReadFile()前,先调用这个HasDataAvailable方法,只有返回true时再执行读取操作,就能避免无数据时的阻塞。

二、非阻塞式ReadFile()替代方案

如果不想每次读取前都做检查,你可以使用异步重叠IO的方式调用ReadFile(),这样即使没有数据,也不会阻塞主线程:

  1. 打开HID设备句柄时,一定要加上FILE_FLAG_OVERLAPPED标志,示例:
IntPtr hidHandle = CreateFile(devicePath, 
    FileAccess.ReadWrite, 
    FileShare.ReadWrite, 
    IntPtr.Zero, 
    FileMode.Open, 
    FileAttributes.Normal | FileFlags.FILE_FLAG_OVERLAPPED, 
    IntPtr.Zero);
  1. 使用Overlapped结构进行异步读取:
public async Task<int> ReadHidDataAsync(IntPtr hidHandle, byte[] buffer)
{
    var overlapped = new NativeOverlapped();
    var eventHandle = new ManualResetEvent(false);
    overlapped.EventHandle = eventHandle.SafeWaitHandle.DangerousGetHandle();
    
    bool result = ReadFile(hidHandle, buffer, (uint)buffer.Length, out uint bytesRead, ref overlapped);
    
    if (!result && Marshal.GetLastWin32Error() == ERROR_IO_PENDING)
    {
        // 等待异步操作完成或取消
        await Task.Run(() => eventHandle.WaitOne());
        GetOverlappedResult(hidHandle, ref overlapped, out bytesRead, false);
    }
    
    eventHandle.Dispose();
    return (int)bytesRead;
}

这种方式下,读取操作会在后台完成,不会阻塞UI线程,也不需要轮询超时,避免了CPU占用过高的问题。

如果你之前尝试FileStream遇到问题,可能是没有正确开启异步模式。试试这样创建FileStream

var stream = new FileStream(hidHandle, FileAccess.Read, bufferSize, true);
// 然后使用stream.ReadAsync()进行异步读取

三、检测HID设备的开机/关机状态

要检测已连接HID设备的开关机状态,有两种可靠的方式:

1. 监听Windows设备通知

通过监听WM_DEVICECHANGE消息,当设备状态变化(比如开机、关机)时,系统会发送这个通知。你可以在WinForms/WPF窗口中重写消息处理方法:

protected override void WndProc(ref Message m)
{
    const int WM_DEVICECHANGE = 0x0219;
    const int DBT_DEVNODES_CHANGED = 0x0007;
    
    if (m.Msg == WM_DEVICECHANGE)
    {
        if (m.WParam.ToInt32() == DBT_DEVNODES_CHANGED)
        {
            // 这里重新枚举HID设备,检查目标设备的状态
            CheckHidDeviceStatus();
        }
    }
    base.WndProc(ref m);
}

当收到DBT_DEVNODES_CHANGED通知时,重新枚举系统中的HID设备,对比目标设备的VID/PID或路径,判断它是否处于活跃状态。

2. 定期发送检测包(如果设备支持)

如果你的HID设备支持接收小的检测命令(比如获取设备状态的指令),可以每隔一段时间发送一个这样的数据包,若能收到响应则说明设备开机,否则可能处于关机状态。这种方式需要设备协议的支持,但准确性很高。

内容的提问来源于stack exchange,提问作者Gaizka Manso Fernandez

火山引擎 最新活动