Wpf DispatcherTimer.Tick 被同一线程中的鼠标挂钩事件中断

Wpf DispatcherTimer.Tick interupted by mouse hook event in same thread

我创建了一个线程,该线程具有用于处理低级鼠标挂钩事件的调度程序 运行,并基于该调度程序创建了一个 DispatcherTimer。

当 DispatcherTimer.Tick() 被触发时,我模拟了另一个鼠标按下消息。然后发生了一些奇怪的事情: Dispatcher.Tick() 被中断,鼠标挂钩事件在 同一线程 中触发。鼠标钩子事件处理完成后,DispatcherTimer.Tick()继续结束

据我了解,dispatcher会一个接一个的处理任务,所以它应该完整的执行完DispatcherTimer.Tick()方法,然后才是其他的hook事件。

这种行为正常吗? 无论如何我可以确保 DispatcherTimer.Tick() 在钩子事件触发之前完全执行?


正在创建调度程序线程:

public static class DispatcherBuilder 
    {
        public static Dispatcher Build()
        {
            Dispatcher dispatcher = null;
            var manualResetEvent = new ManualResetEvent(false);
            var thread = new Thread(() =>
            {
                dispatcher = Dispatcher.CurrentDispatcher;
                var synchronizationContext = new DispatcherSynchronizationContext(dispatcher);
                SynchronizationContext.SetSynchronizationContext(synchronizationContext);

                manualResetEvent.Set();

                try
                {
                    Dispatcher.Run();
                }
                catch 
                {
                    // ignore
                }
            }, maxStackSize:1);
            thread.Priority = ThreadPriority.Normal;
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
            manualResetEvent.WaitOne();
            manualResetEvent.Dispose();
            return dispatcher;
        }
    }

创建计时器和挂钩设置的代码:

// creating disapatcher:

            _hookDispatcher = DispatcherBuilder.Build();




// creating timer on the dispatcher
            _mouseDownTimer = new DispatcherTimer(DispatcherPriority.Normal, _hookDispatcher);
            _mouseDownTimer.Tick += new EventHandler(mouseDownTimer_Tick);
            _mouseDownTimer.Interval = TimeSpan.FromMilliseconds(_dataService.Settings.RightBtnPopupDelayMs);



// create hook

            _hookDispatcher.Invoke(() =>
            {
                _mouseHook = new MouseHook();

                _mouseHook.MouseUp += MouseHookOnMouseUp;
                _mouseHook.MouseDown += MouseHookOnMouseDown;
                _mouseHook.MouseWheel += MouseHookOnMouseWheel;
                _mouseHook.MouseMove += MouseHookOnMouseMove;
                _mouseHook.MouseHWheel += MouseHookOnMouseHWheel;

                _mouseHook.Start();
            });

以下是一些 Debug.WriteLine() 输出:


177,250 MouseDown  Right Thread:14
177,250 MouseDown Right Captured Thread:14

// DeispatcherTimer.Tick() Started
177,360  Timer  Tick  Begin  Thread:14   -- in DeispatcherTimer.Tick()
177,360  Sending RightButtonDown  -- in DeispatcherTimer.Tick()

// MouseHookOnMouseUp() called in same thread
177,485 MouseUp  Right Thread:14  -- in MouseHookOnMouseUp()
177,500 MouseUp Right Captured Thread:14 -- in MouseHookOnMouseUp()

// MouseHookOnMouseDown() called in same thread
177,500 MouseDown  Right Thread:14  -- in MouseHookOnMouseDown()

// Returned to DeispatcherTimer.Tick() in same thread
177,500  Timer Tick End -- in DeispatcherTimer.Tick()


177,500 MouseDown  Right Thread:14

一切似乎都还好。

"When DispatcherTimer.Tick() was fired, I simulated another mouse down message. Then something strange happend: The Dispatcher.Tick() was interupted and mouse hook event fired in the same thread. [...]"

一般情况下,事件处理程序在引发事件的同一线程上执行,除非事件源或处理程序本身将执行编组到不同的线程。
由于您已在 _hookDispatcher 上明确地将 DispatcherTimer 设置为 运行,Tick 处理程序会引发事件,因此 _hookDispatcher 上的 运行s ] 线。因此,Tick 处理程序和由此处理程序引发的所有事件及其相应的事件处理程序在同一线程上执行 - DispatcherTimer 关联的线程。

这回答了您的第一个问题,为什么事件处理程序在 DispatcherTimer 执行的同一线程上执行。

"[...] After the mouse hook event processed, the DispatcherTimer.Tick() continues to end.

From what I understand, the dispatcher will process tasks one by one, so it should execute the DispatcherTimer.Tick() method completely, then other hook events."

事件始终同步(WPF 不实现异步事件)。

1) Tick 处理程序将执行。
2) 此处理程序引发鼠标事件。
3) 事件委托被同步调用,这意味着Tick处理程序的延续是"suspended".
4) 调用鼠标事件委托意味着所有注册的回调也被调用。
5) 在调用最后一个回调后,Tick 处理程序可以继续执行。

所以你的观察是正确的,但这种行为在这种情况下是绝对正常的。

如果您希望 Tick 处理程序在处理鼠标事件时继续执行,您应该在后台线程上引发鼠标事件。但是由于处理输入事件以执行 UI 相关工作,这些处理程序很可能会调用 Dispatcher 来访问 UI 资源(或任何 DispatcherObject)。在后台线程上执行此类处理程序,强制处理程序再次与 UI 线程同步太昂贵而且通常没有任何意义。

推荐的解决方案是使用 Dispatcher.InvokeAsync:

引发事件
private void OnTick(object sender, EventArgs e)
{
  Dispatcher.InvokeAsync(RaiseMouseEvent, DispatcherPriority.Background);
}

Dispatcher.InvokeAsync 异步执行。因此,在调用对象(Tick 处理程序)被调用后,立即控制 returns。