我使用第一个代码块在单独的线程上执行低级鼠标挂钩。它实际上是这样的(信不信由你),因为订阅行为初始化了钩子。使用钩子我需要能够阻止调用事件的方法,以便我可以设置一个值来改变它的执行过程。这就是为什么我不能简单地将事件处理程序卸载到另一个线程的原因。
我的问题是,即使这有效,还有另一种方法可以避免DoEvents
吗?
DoEvents
是否可能仅适用于自己线程上的事件,或者此调用是否会影响我的GUI线程?据我所知,它似乎根本不会影响我的GUI。
注意:如果不调用Sleep
,CPU将显着增加
注意:如果没有DoEvents
挂钩消息,请强制操作系统断开挂钩。
编辑:我创建了一个示例项目,以便您可以对此进行测试。该应用程序将在单独的线程上启动鼠标挂钩并捕获鼠标右键单击并通过消息框让您知道它。您可以使用以下链接获取该项目。
该示例显示您可以阻止GUI线程并仍然处理挂钩而没有确认挂钩在其自己的线程上的问题。
https://github.com/mzomparelli/Threaded-Low-Level-Mouse-Hook-Example
我现在开始认为这是对DoEvents
的有效使用,尽管许多声称DoEvents
总是不好。
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;
}
以下是我的HookProc片段,它会创建事件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);
}
这是我的事件处理程序。
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;
}
}
答案 0 :(得分:1)
多线程的问题在于,无法保证主线程不会发送比工作线程可以处理的事件更多的事件,或者工作线程将缺乏&#34;饥饿&#34;由于缺乏任何事情。这就是为什么您的代码具有丑陋的Sleep
和DoEvents
调用。
您需要的是同步机制。
在这种情况下,我建议您遵循生产者 - 消费者模式,这需要一个队列。在这种情况下,我建议您为队列使用BlockingCollection。
阻塞集合将允许主线程向其添加事件,并提供允许工作线程从中获取事件的方法。如果没有事件,集合将阻止工作线程,直到有一个可用。
首先,声明一个用于保存事件的数据结构:
struct Event
{
object sender;
EventArgs e;
}
然后声明你的队列:
private BlockingCollection<Event> _queue = new BlockingCollection<Event>();
在主线程中,使用添加到队列的普通事件处理程序处理事件:
private OnMouseAction(object sender, EventArgs e)
{
_queue.Add(new Event {sender = sender, e = e});
}
在你的工作线程中,只需读取队列并对其进行操作:
private void MouseHookWorker(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
var event = _queue.Take(token);
ProcessEvent(event.sender, event.e);
}
}
catch (OperationCanceledException ex)
{
}
}
在ProcessEvent
中实现真正的工作(无论是什么)。
要停止工作线程,您可以发出取消令牌信号,或者使用_queue.CompleteAdding();
CancellationToken是可选的,但可能是个好主意。如果您不知道如何使用它,请参阅this question。
答案 1 :(得分:0)
使用现有代码,您将在新创建的线程上附加事件,但这并不意味着新线程将处理事件。事实上它并没有。无论导致事件被引发的任何线程都将继续处理该事件。这就是您需要在代码中进行DoEvents
次调用的原因。
在引发事件后,您需要一种方法将事件编组到后台线程。
如果我是你,我会使用Microsoft的Reactive Framework(Rx)。只是NuGet&#34; System.Reactive&#34;主要位和&#34; System.Reactive.Windows.Forms&#34;对于WinForms位,以及&#34; System.Reactive.Windows.Threading&#34;对于WPF位。
然后你可以这样做:
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);
此代码处理事件,但是使用.ObserveOn(Scheduler.Default)
调用将事件推送到后台线程。
在这种情况下,我使用了Windows窗体,因此.ObserveOn(this)
(this
是当前窗体)将回调推送回UI线程。
因此,当我运行此代码并在表单上移动鼠标时,我得到一个这样的输出:
!1 Th4 Th4 UI1 UI1 Th4 UI1 Th4 UI1 Th4 UI1 Th4 UI1
应该相当清楚地看到此代码正确地将调用推送到后台线程然后再返回到UI。
不会发生阻塞。
要分离事件处理程序,只需调用subscription.Dispose();
。
在我的代码中,我安装了NuGet软件包&#34; System.Reactive&#34;和&#34; System.Reactive.Windows.Forms&#34;,并添加了这些using
s:
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Concurrency;
using System.Threading;
试试这段代码:
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));
这将阻止传入的呼叫者,并在将呼叫推送到另一个线程上的EventArgs.Handled = true
之前尽快设置ExecuteAction(x.iHotkey)
。