等待两个近同步事件的策略
我正在编写一个库,它使用 Win32 API 处理击键事件。每次发生击键时,两个单独的事件几乎同时发生 (这个 和 此)。在我的库中,我有两个单独的线程,一个用于每种事件(钩子和原始事件)。
线程 1(挂钩)等待线程 2(原始)获取其数据,然后再继续。目前它使用类似屏障的机制来实现这一点。因此,这两个事件都必须在其中一个事件被实际处理之前进入。到目前为止,一切都很好。
但有一个问题(当然)。在理想的世界中,我可以保证始终获得这两个事件。不幸的是,由于我不明白的原因,有时 Windows 决定不向我传递两个事件之一(通常另一个应用程序会暂时干扰输入)。因此,如果我输入“Hello world”,线程 1 可能正在处理“H”,而线程 2 可能从未获得“H”事件,并跳到“e”。因此,事件变得不同步,一切都乱了套。
基本上我想要的是:我想以一种有意义的方式将事件配对。如果线程 1 收到“H”事件,而线程 2 收到“e”,则它应该 (1) 尝试等待正确的“H”事件,或者 (2) 超时(是的,线程 1 可以正常失败如果必须的话)。因为我知道这些事件都应该在某个时间窗口内进入,所以我认为这使得这是一个实时编程任务。
我对实时编程一无所知。是否已经有解决方案/数据结构?如果有,它们是什么?如果不是,那么解决此类问题的一般方法是什么(保持两个时间上接近的事件同步)?
谢谢。
I have a library that I'm writing which processes keystroke events using the Win32 API. Every time a keystroke happens, two separate events occur almost simultaneously (this and this). In my library I have two seperate threads, one for each kind of event (hook and raw).
Thread 1 (hook) waits on Thread 2 (raw) to get its data before it proceeds. It currently uses a barrier-like mechanism to achieve this. So both events must come in before either one is actually processed. So far so good.
But there's a problem (of course). In an ideal world, I'd be guaranteed to get both events all the time. Unfortunately for reasons I don't understand, sometimes Windows decides not to deliver ONE of the two events to me (often times another app temporarily is screwing with the input). And so if I type "Hello world", Thread 1 may be processing "H" while Thread 2 may have never gotten the "H" event, and skips to "e". So the events get out of sync and all hell breaks loose.
Basically what I'd like is this: I want to pair the events in a way that makes sense. If Thread 1 gets an "H" event, and Thread 2 gets an "e", it should either (1) try to wait for the correct "H" event, or (2) time out (yes, Thread 1 can gracefully fail if it has to). Since I know that the events should both be coming in within a certain time window, I suppose that makes this a real-time programming task.
I know positively nothing about real-time programming. Are there already solutions / data structures for this? If so, what are they? If not, what would the general approach be for this type of problem (keeping two temporally close events in sync)?
Thanks.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我不是 win32 方面的专家,但我不禁觉得,如果你输掉了事件,无论如何,没有什么是完全可靠的。据我了解,您试图根据原始物理键盘来控制键盘事件的去向(即它们去往哪个窗口) - 也许最好不要过滤事件,只需将它们注入第一个地方。
也就是说,注册一个低级键盘挂钩< /a> 简单地删除所有注入的事件。现在使用原始键盘事件处理程序将模拟的 WM_KEYUP/WM_KEYDOWN 消息注入到最终应该接收它们的任何窗口。
但是,如果做不到这一点,我们将需要(以某种方式)同步 WM_INPUT 和 WM_KEY* 事件流。我假设您已经弄清楚如何在注入的钩子 DLL 和“主”进程中的 WM_INPUT 侦听器之间共享数据。我们将从定义一个熟密钥流互斥体开始。处理 WM_KEY* 挂钩的应用程序将在进行进一步处理之前立即获取此互斥体,以避免与也可能正在处理密钥的其他进程竞争。
您的 WM_INPUT 处理进程可以开始将通过 WM_INPUT 接收到的键码按顺序写入共享内存环形缓冲区(由命名互斥锁同步)。每个键码应该有一个时间戳; WM_KEY* 挂钩将忽略旧的键码。 WM_KEY* 处理程序将首先检查其事件是否与缓冲区中最早的未过期事件匹配;如果是,则应向主进程发送通知,并且挂钩将执行所需的任何处理。没问题。
如果环形缓冲区中没有数据,则可以使用命名信号量来休眠。信号量的计数应该是环形缓冲区中剩余条目数量的乐观计数;获取计数赋予进程从缓冲区中出列一项(如果存在)的权利和义务,但如果缓冲区为空,则进程必须丢弃环互斥体并返回等待信号量。然后,钩子过程可以在互斥体上的等待上设置一个(短!)超时;如果超时到期,钩子应认为存在不同步,并继续正常处理。然后让钩子在短时间内禁用自身可能是一个好主意,以避免减慢快速打字的速度。
如果钩子从环形缓冲区读取的键码与其发送的击键不匹配,则说明某处存在不同步。对此的处理可能取决于您正在做什么;例如,如果您正在实现热键系统,一种方法是简单地删除所有原始事件,并将挂钩转变为纯粹的传递,直到所有键盘在短时间内保持沉默。
请记住,由于您正在处理不同步的线程,每个线程都会看到多个不同步事件流的不同部分,因此不同步始终是一个问题。事件注入可能仍然是避免这种情况的好方法,因为它为您提供了一个可以提供事件排序的同步点。
I'm not an expert in win32, but I can't help but feel that if you're losing events, nothing's going to be completely reliable in any case. As I understand it, you're trying to control where keyboard events go (ie, to which window they go) based on the physical keyboard of origin - perhaps it would be better to, instead of filtering events, simply inject them in the first place.
That is, register a low-level keyboard hook that simply drops all injected events. Now use your raw keyboard event handler to inject emulated WM_KEYUP/WM_KEYDOWN messages to whatever window should receive them in the end.
Failing that, however, we'll need to (somehow) synchronize the WM_INPUT and WM_KEY* event streams. I'll assume you're already worked out how to share data between your injected hook DLL, and the WM_INPUT listener in your 'master' process. We'll start out by defining a cooked-keystream mutex. An application processing a WM_KEY* hook will immediately take this mutex before doing further processing, in order to avoid racing with other processes which may also be processing keys.
Your WM_INPUT-handling process can begin writing to a shared-memory ring buffer (synchronized by a named mutex) the keycodes received over WM_INPUT in order. Each keycode should have a timestamp; WM_KEY* hooks will ignore old keycodes. The WM_KEY* handlers will first check whether their event matches the oldest non-expired event in the buffer; if it is, a notification shall be sent to the master process, and the hook will do whatever processing is desired. No problem.
If there is no data in the ring buffer, a named semaphore can be used to sleep. The semaphore's count should be an optimistic count of the number of entries remaining in the ring buffer; acquiring a count gives a process the right and obligation to deque one item from the buffer if present, but if the buffer is empty, the process must drop the ring mutex and go back to waiting on the semaphore. The hook procedure can then put a (short!) timeout on its wait on the mutex; if the timeout expires, the hook shall consider there to have been a desync, and proceed with normal processing. It may be a good idea to then have the hook disable itself for a short interval, to avoid slowing down fast typing.
If the hook reads from the ring buffer a keycode that does not match the keystroke it was sent, then there has been a desync somewhere. The handling of this may depend on what you're doing; if you're implementing a hotkey system, for example, one approach would be to simply drop all raw events, and turn the hook into a pure pass-through, until all keyboards are silent for a short interval.
Keep in mind that, since you're dealing with unsynchronized threads, each seeing a different slice of multiple, unsynchronized event streams, desyncs will always be a problem. Event injection may still be a good way to avoid this, as it gives you a single synchronization point that can provide an ordering on events.