wpfdispatchertimer

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


I have a created a thread that has a dispatcher run for processing low level mouse hook events, and created a DispatcherTimer based on this dispatcher.

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. 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.

Is this behaivor normal? Is there anyway that I can ensure DispatcherTimer.Tick() was executed completely before hook event fired?


Creating dispatcher thread:

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;
        }
    }

Code for creating timer and hook setup:

// 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();
            });

The following is some Debug.WriteLine() output:


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


Solution

  • Everything seems to be alright.

    "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. [...]"

    In general event handlers are executed on the same thread the event was raised on, except execution is marshalled to a different thread by the event source or the handler itself.
    Since you had explicitly setup the DispatcherTimer to run on the _hookDispatcher, the Tick handler, which raises the event, runs on the _hookDispatcher thread. Therefore the Tick handler and all events raised by this handler and their corresponding event handlers execute on the same thread - the thread the DispatcherTimer is associated with.

    This answers your first question, why the event handlers are executed on the same thread the DispatcherTimer executes on.

    "[...] 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."

    Events are always raised synchronously (WPF doesn't implement asynchronous events).

    1) The Tick handler will execute.
    2) This handler raises a mouse event.
    3) The event delegate is invoked synchronously, which means the continuation of the Tick handler is "suspended".
    4) Invocation of the mouse event delegate means all registered callbacks are invoked too.
    5) After the last callback was invoked the Tick handler can continue execution.

    So your observations are correct, but this behavior is absolutely normal in this context.

    If you want the Tick handler continue to execute while the mouse event will be handled, you should raise the mouse event on a background thread. But since input events are handled to do UI related work, it is very likely that those handlers will invoke the Dispatcher to access UI resources (or any DispatcherObject). Executing such handlers on a background thread, forcing the handlers to synchronize with the UI thread again is too expensive and usually doesn't make any sense.

    The recommended solution is to raise the events using Dispatcher.InvokeAsync:

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

    Dispatcher.InvokeAsync executes asynchronous. Therefore, control returns immediately to the calling object (the Tick handler) after it is called.