2010-08-27 133 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。