-1

I'm using the first code block to execute a low-level mouse hook on a separate thread. It actually works like this (believe it or not) because the act of subscribing initializes the hook. And with a hook I need to be able to block the method calling an event so that I can set a value to alter its course of execution. This is the reason why I can't simply off-load the event handler to another thread.

My problems is, even though this works, is there another way that can avoid DoEvents?

Is it possible that DoEvents only applies to events on its own thread or does this call affect my GUI thread? It does not appear to affect my GUI at all as far as I can tell.

Note: Without the call to Sleep the CPU will increase significantly.
Note: Without DoEvents the hook messages build up and force the OS to disconnect the hook.

EDIT: I created a sample project so you guys can test this. The app will start a mouse hook on a separate thread and capture a mouse right-click and let you know it did so via a message box. You can get that project using the below link.

The sample shows that you can block the GUI thread and still handle the hook without issues which confirms that the hook is on its own thread.

https://github.com/mzomparelli/Threaded-Low-Level-Mouse-Hook-Example

I am now starting to think that this is a valid use of DoEvents despite the many claims that DoEvents is always bad.

private static bool blnStopMouseHook = false;
        public static void StartMouseHook()
        {
            if (MouseHook == null)
            {
                blnStopMouseHook = false;
                MouseHook = new Thread(new ThreadStart(() => { MouseHookThread(); }));
                MouseHook.SetApartmentState(ApartmentState.STA);
                MouseHook.Start();
            }
        }

        public static void StopMouseHook()
        {
            blnStopMouseHook = true;
            MouseHook.Join();
            MouseHook = null;
        }

        private static void MouseHookThread()
        {
            HookManager.MouseWheel += HookHandlers.HookManagerOnMouseWheel;
            HookManager.MouseClickExt += HookHandlers.HookManagerOnMouseClickExt;
            do
            {
                System.Threading.Thread.Sleep(1);
                Application.DoEvents();
            } while (blnStopMouseHook == false);

            HookManager.MouseWheel -= HookHandlers.HookManagerOnMouseWheel;
            HookManager.MouseClickExt -= HookHandlers.HookManagerOnMouseClickExt;

        }

Below is a snippet of my HookProc which creates the event HookManagerOnMouseWheel

private static int MouseHookProc(int nCode, int wParam, IntPtr lParam)
        {
            if (nCode >= 0)
            {
                //Marshall the data from callback.
                MouseLLHookStruct mouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));

                switch (wParam)
                {
                    case WM_MOUSEWHEEL:
                        mouseDelta = (short)((mouseHookStruct.MouseData >> 16) & 0xffff);
                       break;
                }

                //generate event 
                MouseEventExtArgs e = new MouseEventExtArgs(
                                                   button,
                                                   clickCount,
                                                   mouseHookStruct.Point.X,
                                                   mouseHookStruct.Point.Y,
                                                   mouseDelta);



                //Wheel was moved
                if (s_MouseWheel!=null && mouseDelta!=0)
                {
                    s_MouseWheel.Invoke(null, e);
                }



                //If someone listens to move and there was a change in coordinates raise move event

                if (e.Handled)
                {
                    return -1;
                }
            }

            //call next hook
            return CallNextHookEx(s_MouseHookHandle, nCode, wParam, lParam);
        }

This is my event handler.

public static void HookManagerOnMouseWheel(object sender, MouseEventExtArgs mouseEventArgs)
        {

            int iHotkey;
            int iHotkey2;
            string keyCombination = CurrentModifiers();
            string keyCombination2 = CurrentModifiers();

            if (Window.Taskbar().IsMouseOver() || Window.Taskbar2().IsMouseOver())
            {
                //Create combination string

                if (mouseEventArgs.Delta < 0)
                {
                    keyCombination = keyCombination + "+MOUSE-TASKBAR-SCROLL-DOWN";
                    keyCombination2 = keyCombination2 + "+MOUSE-ANYWHERE-SCROLL-DOWN";
                }
                else
                {
                    keyCombination = keyCombination + "+MOUSE-TASKBAR-SCROLL-UP";
                    keyCombination2 = keyCombination2 + "+MOUSE-ANYWHERE-SCROLL-UP";
                }

                iHotkey = GLOBALS.hotkeys.FindIndex(l => l.HotkeyString() == keyCombination);
                iHotkey2 = GLOBALS.hotkeys.FindIndex(l => l.HotkeyString() == keyCombination2);
                if (iHotkey >= 0)
                {
                    ExecuteAction(iHotkey);
                    mouseEventArgs.Handled = true;
                    return;
                }
                else if (iHotkey2 >= 0)
                {
                    ExecuteAction(iHotkey2);
                    mouseEventArgs.Handled = true;
                    return;
                }
            }

            if (mouseEventArgs.Delta < 0)
            {
                keyCombination = keyCombination + "+MOUSE-ANYWHERE-SCROLL-DOWN";
            }
            else
            {
                keyCombination = keyCombination + "+MOUSE-ANYWHERE-SCROLL-UP";
            }

            iHotkey = GLOBALS.hotkeys.FindIndex(l => l.HotkeyString() == keyCombination);
            if (iHotkey >= 0)
            {
                ExecuteAction(iHotkey);
                mouseEventArgs.Handled = true;
                return;
            }


        }
Michael Z.
  • 1,453
  • 1
  • 15
  • 21
  • Why a sleep inside the loop? – Suraj S Jul 11 '17 at 01:14
  • Because without it the `DoEvents` is executed too much. It needs a break or a significant CPU increase will occur. This is the area I am concerned with. – Michael Z. Jul 11 '17 at 01:16
  • 3
    Please never ever ever use `Application.DoEvents()`. It's only in the framework for backward compatibility with VB6. It'll end up causing you more bugs than it solves. – Enigmativity Jul 11 '17 at 01:18
  • also i don't see any point `sleep(1)` – Lei Yang Jul 11 '17 at 01:19
  • @Enigmativity can you help me avoid it here? I don't see it causing any bugs like this though and this is the only case I am using it. – Michael Z. Jul 11 '17 at 01:19
  • Can you explain what this code is trying to do? What's the reason behind it? – Enigmativity Jul 11 '17 at 01:21
  • I don't quite understand what you're up to but this might help out. https://stackoverflow.com/questions/1115397/application-doevents-when-its-necessary-and-when-its-not – Suraj S Jul 11 '17 at 01:22
  • @MichaelZ. - I think you missed my point. What's the reason that you think you need the event handlers on another thread? What work are you doing that requires it? What's your business need? – Enigmativity Jul 11 '17 at 01:55
  • @Enigmativity to prevent GUI execution from interfering with the mouse hook events. A mouse hook absolutely needs to run on a separate thread unless you want to risk it interfering. When interference happens then the call to getNextHook is delayed meaning you will see mouse lag. What I mean by interference is GUI execution. My code works perfectly and I'm just wondering another way. – Michael Z. Jul 11 '17 at 01:57
  • @MichaelZ. - I would like to see what you're doing on the other thread. I think I have an excellent alternative, but I don't understand how you are hanging this all together at the moment. – Enigmativity Jul 11 '17 at 02:10
  • @MichaelZ. - It's hard to work with your code. You should at least post a [mcve] for us to work from. – Enigmativity Jul 11 '17 at 02:12
  • @MichaelZ. - What is `HookManager`? Where does it come from? – Enigmativity Jul 11 '17 at 02:35
  • 1
    I am not sure this example handles events in a separate thread. When an event fires it'll go to the same thread as the code that raises it. Unless there is something unusual about `HookManager`, it doesn't matter that you bound the handler in a different thread; that only sets the entry point, not thread affinity. – John Wu Jul 11 '17 at 02:44
  • OP can you confirm that your `HookManager` is a wrapper for the user32.dll calls as outlined in [this example](https://support.microsoft.com/en-us/help/318804/how-to-set-a-windows-hook-in-visual-c-.net)? If not, what is it? Can you post the code? – John Wu Jul 11 '17 at 02:51
  • @JohnWu yes! it's this API except I am using `WH_MOUSE_LL` – Michael Z. Jul 11 '17 at 02:53
  • 1
    OK I had my doubts but this is actually a *really* good question for S.O. +1 – John Wu Jul 11 '17 at 03:19
  • @JohnWu is this setup that bad? It actually works fine and it looks clean. It feels wrong with `DoEvents`. I don't see any bug potential here. – Michael Z. Jul 11 '17 at 03:27
  • I feel exactly the same way. The default message pump deals with idle time by calling an IdleProc (see [this page](https://msdn.microsoft.com/en-us/library/3dy7kd92.aspx)) and maybe you could do something similar. I don't think the `Sleep` is terrible, but `DoEvents` is a worry. Have you tried removing the `DoEvents` call? Does everything stop working if you do that? – John Wu Jul 11 '17 at 03:34
  • Without `DoEvents` it causes buildup of messages not being processed and then the OS disconnects my hook. – Michael Z. Jul 11 '17 at 03:46
  • @JohnWu I added a link to a fully working sample mouse hook project. Visual Studio 2017. It uses the exact implementation I have shown in this question. – Michael Z. Jul 12 '17 at 22:26

2 Answers2

1

The trouble with multithreading is that there is no guarantee that the main thread won't send more events than the worker thread can handle, or that the worker thread will be "starved" by the lack of anything to do. That is why your code has those ugly Sleep and DoEvents calls.

What you need is a synchronization mechanism.

I would suggest in this case that you follow a producer-consumer pattern, which requires a queue. I'd recommend in this case you use a BlockingCollection for your queue.

A blocking collection will allow the main thread to add events to it and provides methods that allow the worker thread to take events from it. If there are no events, the collection will block the worker thread until one is available.

So first, declare a data structure for holding events:

struct Event 
{
    object sender;
    EventArgs e;
}

Then declare your queue:

private BlockingCollection<Event> _queue = new BlockingCollection<Event>();

In your main thread, handle your events with a normal event handler that adds to the queue:

private OnMouseAction(object sender, EventArgs e)
{
    _queue.Add(new Event {sender = sender, e = e});
}

And in your worker thread, just read the queue and act on it:

private void MouseHookWorker(CancellationToken token)
{
    try
    {
        while (!token.IsCancellationRequested)
        {
            var event = _queue.Take(token);
            ProcessEvent(event.sender, event.e);
        }
    }
    catch (OperationCanceledException ex)
    {
    }
}     

And implement the real work (whatever that is) in ProcessEvent.

To stop the worker thread, you can either signal the cancellation token, or simple stop the queue with _queue.CompleteAdding();

The CancellationToken is sort of optional, but probably a good idea. If you don't know how to use it, see this question.

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Thanks for the answer. Will this allow me to handle the event in real-time? I need to be able to set a variable in the EventArgs that goes back to the calling method. Specifically, it's a mouse hook, and I want to be able to not call `CallNextHookEx` – Michael Z. Jul 11 '17 at 02:24
  • 1
    @MichaelZ. - Windows isn't a real-time operating system. You can't do anything in real-time. – Enigmativity Jul 11 '17 at 02:26
  • What I meant was that I need the event handler to block the calling method. – Michael Z. Jul 11 '17 at 02:27
  • It seems this is creating a new event in response to the real event. – Michael Z. Jul 11 '17 at 02:29
  • A mouse hook is kind of real-time. Events come in fast and have to be pushed back out quickly to avoid issues. – Michael Z. Jul 11 '17 at 02:30
  • @Michael, no, that is not possible. If you hand off control to another thread, that is a non-blocking call (otherwise why have a different thread?) and there is no way to return a value to the caller, which will have moved on. – John Wu Jul 11 '17 at 02:43
  • My current solution starts the handlers on a thread and they block the calling method as expected. I'm using a thread to prevent GUI code from blocking the event handler. I do in fact want the event handler to block its calling method. Adding these handlers creates the hook. This means the hook is on a new thread as intended. – Michael Z. Jul 11 '17 at 02:50
  • You will need to explain how that works, or post the code for `HookManager` and related, in order for us to analyze your problem accurately. – John Wu Jul 11 '17 at 02:52
  • Thanks for this answer. I will definitely put it to use. I think you can agree after seeing my updated question that this is not the correct answer. A great answer nonetheless! – Michael Z. Jul 11 '17 at 03:24
0

With your existing code you are attaching the events on the newly created thread, but that doesn't mean that the new thread will handle the events. In fact it doesn't. Whatever thread that causes the event to be raised will go on to handle the event. That's why you need the DoEvents calls in your code.

You need a way to marshal the events to the background thread after the event is raised.

If I were you I'd use Microsoft's Reactive Framework (Rx) for this. Just NuGet "System.Reactive" for the main bits and "System.Reactive.Windows.Forms" for the WinForms bits, and "System.Reactive.Windows.Threading" for the WPF bits.

Then you can do things like this:

IObservable<EventPattern<MouseEventArgs>> mouseMoves =
        Observable
            .FromEventPattern<MouseEventHandler, MouseEventArgs>(
                h => this.MouseMove += h,
                h => this.MouseMove -= h);

IDisposable subscription =
    mouseMoves
        .ObserveOn(Scheduler.Default)
        .Do(ep =>
        {
            Console.WriteLine("Th" + Thread.CurrentThread.ManagedThreadId);
        })
        .ObserveOn(this)
        .Subscribe(ep =>
        {
            Console.WriteLine("UI" + Thread.CurrentThread.ManagedThreadId);
        });

Console.WriteLine("!" + Thread.CurrentThread.ManagedThreadId);

This code handles events, but pushes the event on to the a background thread with the .ObserveOn(Scheduler.Default) call.

In this case I've used Windows Forms, so the .ObserveOn(this) (with the this being the current form) pushes the call back to the UI thread.

So, when I run this code and move the mouse around on the form I get an output like this:

!1
Th4
Th4
UI1
UI1
Th4
UI1
Th4
UI1
Th4
UI1
Th4
UI1

It should be fairly clear to see that this code is correctly pushing the call to a background thread and then back to the UI.

No blocking occurs.

To detach the event handler just call subscription.Dispose();.

In my code I installed NuGet packages "System.Reactive" and "System.Reactive.Windows.Forms", and added these usings:

using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Concurrency;
using System.Threading;

Try this code:

        IDisposable subscription =
        (
            from mm in mouseMoves
            let direction = mm.EventArgs.Delta < 0 ? "DOWN" : "UP"
            let keyCombination = CurrentModifiers() + "+MOUSE-ANYWHERE-SCROLL-" + direction
            let iHotkey = GLOBALS.hotkeys.FindIndex(l => l.HotkeyString() == keyCombination)
            where iHotkey >= 0
            select new { mm.EventArgs, iHotkey }
        )
            .Do(x => x.EventArgs.Handled = true)
            .ObserveOn(Scheduler.Default)
            .Subscribe(x => ExecuteAction(x.iHotkey));

This will block the incoming caller and will set the EventArgs.Handled = true asap, before pushing the call to ExecuteAction(x.iHotkey) on a different thread.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Will this block the calling method `MouseHookProc`? This looks way less readable than what I've got, but if it can perform better then I'll try it. Considering I can figure it out. – Michael Z. Jul 11 '17 at 03:58
  • @MichaelZ. - No, it doesn't block the calling method. What don't you find readable? It's technically only two lines of code. – Enigmativity Jul 11 '17 at 04:05
  • There are easier ways than this to pass the event off to a thread. I need the entire handler on the thread so that it can block the calling method. I'm going to play around with the namespaces you mentioned so I can become more familiar. – Michael Z. Jul 11 '17 at 04:13
  • @MichaelZ. - Why do you need to block the calling thread? Especially when you called `DoEvents` to effectively unblock it? – Enigmativity Jul 11 '17 at 04:15
  • I need to block because I may need to set a variable that will prevent the mouse scroll from going to the next hook. It's a hotkey app, that should be apparent in the code. It uses Win API `WH_MOUSE_LL` as opposed to `RegisterHotkey` – Michael Z. Jul 11 '17 at 04:19
  • @MichaelZ. - That makes sense, but I'm confused then about what you want to run on the other thread. Is it just the actual `ExecuteAction(iHotkey);` calls? The rest of `HookManagerOnMouseWheel` should block the calling thread. Is that right? – Enigmativity Jul 11 '17 at 04:25
  • `ExecuteAction` must return as fast as possible so that the messages don't build up in the hook. So in that method I have a quick switch that executes the action on another thread. The entire handler has to return quickly so there is quick logic and more threads. The first block of code I posted is my attempt to take the entire mouse hook off the GUI thread. – Michael Z. Jul 11 '17 at 04:28
  • @MichaelZ. - It wouldn't be too difficult to change my code to suit what you need. Give me a tick to see what I can come up with. – Enigmativity Jul 11 '17 at 04:31
  • In response to the first line in your answer...the way this is setup, the hook is in fact entirely on a separate thread. I can perform tests that prove it. – Michael Z. Jul 11 '17 at 04:46
  • @MichaelZ. - But it's still the thread that raises the event that it is running on, and not necessarily the thread you created. – Enigmativity Jul 11 '17 at 04:50
  • The updated code looks like it will create a copy of the event and allow the calling method to carry on without allowing me to change the course of the calling method. Where do I put the subscriber? How is this c# lol. I have to think about this. – Michael Z. Jul 11 '17 at 04:54
  • @MichaelZ. - No, it doesn't create a copy of the event. It will block the caller right up until the `.ObserveOn(Scheduler.Default)`. And yes, it's C#. ;-) – Enigmativity Jul 11 '17 at 04:59
  • You're way smarter than me! :) My code looks nothing like this. Right now, this makes me want to pull my hair out lol. I'll see how I can use/test this. Can I just put this in the main program loop? And what about DLLs? I'm trying to stay as a single EXE. – Michael Z. Jul 11 '17 at 05:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/148842/discussion-between-michael-z-and-enigmativity). – Michael Z. Jul 11 '17 at 05:04
  • @MichaelZ. - I just put it in `private void Form1_Load(object sender, EventArgs e)`. You just want to run this code once - not in a loop. – Enigmativity Jul 11 '17 at 05:04