2017-07-11 28 views
-1

我正在使用第一個代碼塊在單獨的線程上執行低級別的鼠標掛鉤。它實際上是這樣工作的(不管你信不信),因爲訂閱行爲會初始化鉤子。並且,我需要能夠阻止調用事件的方法,以便我可以設置一個值來改變其執行過程。這就是爲什麼我不能簡單地將事件處理程序卸載到另一個線程的原因。這是否正確使用DoEvents? - 包含示例項目

我的問題是,即使這有效,是否有另一種方法可以避免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

爲什麼在循環內睡眠? –

+0

因爲沒有它,DoEvents執行得太多,需要休息或重要CPU會增加。這是我關心的領域。 –

+3

請永遠不要使用Application.DoEvents()。它只是在與VB6向後兼容的框架中。它最終會導致更多的錯誤,而不是解決問題。 – Enigmativity

回答

1

多線程的麻煩是沒有保證的主線程不會發送更多的事件比工作線程的處理能力,或者說,工作線程會被「餓死」因缺乏什麼做。這就是爲什麼你的代碼有那些醜陋的SleepDoEvents調用。

你需要的是一個同步機制。

我會建議在這種情況下,你遵循生產者 - 消費者模式,這需要一個隊列。在這種情況下,我建議你爲隊列使用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

+0

感謝您的回答。這是否允許我實時處理事件?我需要能夠在EventArgs中設置一個返回調用方法的變量。具體來說,它是一個鼠標鉤子,我希望能夠不能調用CallNextHookEx# –

+1

@MichaelZ。 - Windows不是實時操作系統。你無法實時做任何事情。 – Enigmativity

+0

我的意思是我需要事件處理程序來阻止調用方法。 –

0

使用您現有的代碼,您將在新創建的線程上附加事件,但這並不意味着新線程將處理事件。事實上它不是。無論導致事件發生的線程將繼續處理事件。這就是爲什麼你需要在代碼中調用DoEvents

您需要一種方法在引發事件後將事件封送到後臺線程。

如果我是你,我會使用微軟的Reactive Framework(Rx)。只需要NuGet「System.Reactive」爲主要位,「System.Reactive.Windows.Forms」爲WinForms位,「System.Reactive.Windows.Threading」爲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 

應該很清楚地看到,該代碼是正確推動呼叫到後臺線程,然後返回到用戶界面。

不發生阻塞。

要分離事件處理程序只需撥打subscription.Dispose();

在我的代碼中,我安裝了NuGet包「System.Reactive」和「System.Reactive.Windows。表格」,並補充這些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)之前一個不同的線程

+0

這會阻止調用方法'MouseHookProc'嗎?這看起來不如我的可讀性好,但如果它能更好地表現,那我就試試看。考慮到我可以弄明白。 –

+0

@MichaelZ。 - 不,它不會阻止調用方法。你不覺得可讀?技術上只有兩行代碼。 – Enigmativity

+0

有比這更簡單的方法將事件傳遞給線程。我需要線程上的整個處理程序,以便它可以阻止調用方法。我將會玩弄你提到的命名空間,這樣我可以變得更加熟悉。 –

相關問題