2010-04-13 64 views
3

我一直在玩一個愛好項目的DataBus類型的設計,我遇到了一個問題。後端組件需要通知UI發生了什麼事。我的總線實現與發送者同步傳遞消息。換句話說,當你調用Send()時,該方法會阻塞,直到所有的處理程序調用。 (這使得調用者可以使用堆棧內存管理來處理事件對象。)WinForm樣式Invoke()in unmanaged C++

但是,請考慮事件處理程序更新GUI以響應事件的情況。如果處理程序被調用,並且消息發送者駐留在另一個線程上,則由於Win32的GUI元素具有線程相關性,處理程序無法更新GUI。 .NET等更多動態平臺允許您通過調用特殊的Invoke()方法將方法調用(和參數)移動到UI線程來處理此問題。我猜他們使用.NET停放窗口或類似的東西來做這些事情。

一種病態的好奇心誕生了:即使我們限制了問題的範圍,我們可以用C++來做到嗎?我們可以比現有的解決方案更好嗎?我知道Qt的功能與moveToThread()功能類似。

通過更好,我會提到,我特別想避免以下形式的代碼:

if(! this->IsUIThread()) 
{ 
    Invoke(MainWindowPresenter::OnTracksAdded, e); 
    return; 
} 

是在每個用戶界面方法的頂部。在處理這個問題時,這種舞蹈在WinForms中很常見。我認爲這種關注應該與領域特定的代碼和一個用於處理它的包裝器對象隔離。

我實現由:

  • DeferredFunction - 仿函數存儲在一個FastDelegate目標方法,而深拷貝單個事件的說法。這是通過線程邊界發送的對象。

  • UIEventHandler - 負責從公共汽車調度單個事件。當調用Execute()方法時,它將檢查線程ID。如果它與UI線程ID(在構建時設置)不匹配,則使用實例,方法和事件參數在堆上分配DeferredFunction。指向它的指針通過PostThreadMessage()發送到UI線程。

  • 最後,鉤子函數用於線程的消息泵用於調用DeferredFunction並取消分配它。或者,我可以使用消息循環過濾器,因爲我的UI框架(WTL)支持它們。

最終,這是一個好主意嗎?整個消息掛鉤的事情讓我很沮喪。目的當然是高貴的,但是我應該知道有什麼缺陷?還是有更簡單的方法來做到這一點?

+1

你爲什麼不在Reflector中打開Invoke,看看他們是如何做到的? :) – 2010-04-13 03:17:35

+0

你知道嗎,我從來沒有想過...明天我會給它看看。 :) – 2010-04-13 03:20:28

回答

3

我已經離開了Win32遊戲很長一段時間,但我們用來實現這一目標的方式是使用PostMessage將一條Windows消息發回到UI線程,然後處理來自那裏的呼叫,傳遞在wParam/lParam中需要其他信息。

事實上,我不會驚訝,如果這是如何在Control.Invoke中處理這個。

更新:我currios所以我檢查反射器,這是我發現。

Control.Invoke調用MarshaledInvoke,它執行一些checkes等,但有趣的調用是RegisterWindowMessage和PostMessage。這樣的事情並沒有發生太大的變化:)

1

的後續信息一點點:

有幾個方法可以做到這一點,每個都有優點和缺點:

  • 最簡單的方法可能是撥打QueueUserAPC()。 APC的解釋有點過於深入,但唯一的缺點是,如果線程被意外置於可警告的等待狀態,他們可能會在未準備好時運行。正因爲如此,我避開了他們。對於短的應用程序,這可能是好的。

  • 第二種方法涉及使用PostThreadMessage(),如前所述。這比QueueUserAPC()好,因爲你的回調對UI線程處於可警告的等待狀態不敏感,但是使用這個API有你的回調沒有被運行的問題。見Raymond Chen's discussion on this。爲了解決這個問題,你需要在線程的消息隊列中加入一個鉤子。

  • 第三種方法是設置一個不可見的,僅限消息的窗口,其WndProc調用延遲調用,並使用PostMessage()作爲回調數據。由於它指向特定的窗口,所以消息不會在模態UI情況下被使用。此外,只有消息的窗口不受系統消息廣播影響(從而防止消息ID衝突)。缺點是它需要比其他選項更多的代碼。