2010-08-27 184 views
3

我有一個小型的客戶端 - 服務器應用程序,其中服務器使用命名管道向客戶端發送一些消息。客戶端有兩個線程 - 主GUI線程和一個「接收線程」,它通過命名管道不斷接收服務器發送的消息。現在每當收到一條消息時,我想發起一個自定義事件 - 但是,該事件不應該在調用線程上處理,而應該在主要的GUI線程上處理 - 而且我不知道該怎麼做(以及是否甚至有可能)。德爾福 - 跨線程事件處理

這是我到目前爲止有:

tMyMessage = record 
    mode: byte; 
    //...some other fields... 
end; 

TMsgRcvdEvent = procedure(Sender: TObject; Msg: tMyMessage) of object; 

TReceivingThread = class(TThread) 
private 
    FOnMsgRcvd: TMsgRcvdEvent; 
    //...some other members, not important here... 
protected 
    procedure MsgRcvd(Msg: tMyMessage); dynamic; 
    procedure Execute; override; 
public 
    property OnMsgRcvd: TMsgRcvdEvent read FOnMsgRcvd write FOnMsgRcvd; 
    //...some other methods, not important here... 
end; 

procedure TReceivingThread.MsgRcvd(Msg: tMyMessage); 
begin 
    if Assigned(FOnMsgRcvd) then FOnMsgRcvd(self, Msg); 
end; 

procedure TReceivingThread.Execute; 
var Msg: tMyMessage 
begin 
    //..... 
    while not Terminated do begin //main thread loop 
    //..... 
    if (msgReceived) then begin 
     //message was received and now is contained in Msg variable 
     //fire OnMsgRcvdEvent and pass it the received message as parameter 
     MsgRcvd(Msg); 
    end; 
    //..... 
    end; //end main thread loop 
    //..... 
end; 

現在我希望能夠創建事件處理程序作爲TForm1類的成員,例如

procedure TForm1.MessageReceived(Sender: TObject; Msg: tMyMessage); 
begin 
    //some code 
end; 

那會不會是在接收線程中執行,但在主UI線程中執行。我特別喜歡接收線程只是觸發事件,並繼續執行,無需等待事件處理程序方法的返回(基本上我需要類似.NET Control.BeginInvoke方法)

我真的是初學者這個(我試圖在幾個小時前學習如何定義自定義事件),所以我不知道這是否可能,或者我做錯了什麼,所以非常感謝您的幫助。

回答

2

你已經有了一些答案了,但他們沒有提到你的問題的困擾部分:

tMyMessage = record 
    mode: byte; 
    //...some other fields... 
end; 

請大家注意,你不能做所有是理所當然的,你可能需要的東西.NET環境當您使用Delphi或其他包裝本機Windows消息處理。您可能希望能夠隨機的數據結構傳遞給一個事件處理程序,但是這是行不通的。原因是需要內存管理。

在.NET中,你可以肯定的是那些不再從任何地方引用的數據結構將被垃圾收集處理掉。在Delphi中你沒有同類型的餘地的,你需要確保的內存分配任何塊也正確釋放。

在Windows的消息接收器可以是一個窗口句柄(HWND),你SendMessage()PostMessage(),或者它是一個線程,你PostThreadMessage()到。在這兩種情況的消息可以攜帶只有兩個數據成員,這兩者都是機器字的寬度,所述第一WPARAM類型的,LPARAM類型的第二)。您不能簡單地發送或張貼任何隨機記錄作爲消息參數。

Delphi使用的所有消息記錄類型具有基本相同的結構,它映射到上面的數據大小限制。

如果你想發送數據到另一個由多於兩個32位大小的變量組成的線程,那麼事情會變得棘手。由於可以發送的值的大小限制,您可能無法發送整個記錄,但只能發送其地址。爲此,您可以在發送線程中動態分配數據結構,將地址作爲消息參數之一傳遞,並將接收線程中的相同參數重新解釋爲具有相同類型的變量的地址,然後將數據用於記錄,並釋放動態分配的內存結構。

因此,根據您需要發送到事件處理程序的數據量,您可能需要更改tMyMessage記錄。這可以起作用,但由於類型檢查對於事件數據不可用,所以它比所需的更困難。

我建議解決這個有點不同。您知道需要將哪些數據從工作線程傳遞到GUI線程。只需創建一個將事件參數數據放入的排隊數據結構,而不是直接將消息發送給他們。使這個隊列成爲線程安全的,即使用關鍵部分保護它,這樣即使從不同線程同時嘗試時,添加或從隊列中移除也是安全的。

要請求新的事件處理,只需將數據添加到您的隊列。只有當第一個數據元素添加到先前的空隊列時,纔會向接收線程發送消息。接收線程應該接收並處理消息,並繼續從隊列中彈出數據元素並調用匹配的事件處理程序,直到隊列再次爲空。爲了獲得最佳性能,應該儘可能短地鎖定隊列,並且在調用事件處理程序時一定要臨時再次解鎖。

0

檢查同步方法的文檔。它專爲像你這樣的任務而設計。

+0

唉。同步暫停所有輔助線程,並在主線程的上下文中執行,這意味着它首先破壞了多線程的許多目的。有更好的方法比同步噸。不過,我沒有投票給你,因爲即使這是一個可怕的答案,它在技術上是一個有效的答案。 :-) – 2010-08-27 17:41:55

+2

誰告訴你這個神話?同步不會阻止「所有」輔助線程。它只會阻塞從它被調用的線程,並且這是由於它的同步特性而發生的。但其他線程繼續運行。 – 2010-08-27 18:03:50

+0

是的,只有調用線程被阻塞。這不是最好的機制,但如果你知道它是如何工作的,你應該沒問題。 – Runner 2010-08-27 18:20:36

2

您應該使用PostMessage的(非同步)或SendMessage函數(同步)API將消息發送到」窗口。您還可以使用某種形式的「隊列」或使用奇妙OmniThreadLibrary」做這個(高recomended)

+1

+1不使用同步。使用後一個例子/發短信真的是有幫助的,尤其是在OP克利裏表示是一個絕對的初學者... – 2010-08-27 18:16:08

+1

...並同步非常適合初學者,因爲它沒有考慮初學者到Windows消息的深度。 – 2010-08-27 18:23:11

1

聲明一個私有成員

FRecievedMessage: TMyMEssage 

和被保護的程序

procedure PostRecievedMessage; 
begin 
    if Assigned(FOnMsgRcvd) then FOnMsgRcvd(self, FRecievedMessage); 
    FRecievedMessage := nil; 
end; 

而且將環路中的代碼更改爲

if (msgReceived) then begin 
    //message was received and now is contained in Msg variable 
    //fire OnMsgRcvdEvent and pass it the received message as parameter 
    FRecievedMessage := Msg; 
    Synchronize(PostRecievedMessage); 
end; 

如果要完全按照asyn請改用PostMessage API。