Delphi - 跨线程事件处理

时间:2010-08-27 16:32:43

标签: multithreading delphi delphi-6

我有一个小型客户端 - 服务器应用程序,其中服务器使用命名管道向客户端发送一些消息。客户端有两个线程 - 主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方法那样)

我是初学者(我试着学习如何在几个小时前定义自定义事件。),所以我不知道它是否可能或者我做错了什么,所以非常感谢提前帮助你。

5 个答案:

答案 0 :(得分:2)

您应该使用PostMessage(asynch)或SendMessage(synch)API向“窗口”发送消息。您也可以使用某种“队列”或使用梦幻般的OmniThreadLibrary来“做到这一点(高度推荐)

答案 1 :(得分:2)

你已经有了一些答案,但他们都没有提到你问题中令人不安的部分:

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

请注意,当您使用Delphi或其他包装器进行本机Windows消息处理时,您无法在.NET环境中执行所有您认为理所当然的事情。您可能希望能够将随机数据结构传递给事件处理程序,但这不起作用。原因是需要内存管理。

在.NET中,您可以确保不再从任何地方引用的数据结构将被垃圾回收处理掉。在Delphi中,你没有相同的余地,你需要确保任何已分配的内存块也被正确释放。

在Windows中,消息接收者可以是HWNDSendMessage()所使用的窗口句柄(PostMessage()),也可以是PostThreadMessage()到的线程。在这两种情况下,消息只能携带两个数据成员,这两个数据成员都是机器字宽,第一个是类型WPARAM,第二个类型是LPARAM)。您不能简单地将任何随机记录作为消息参数发送或发布。

Delphi使用的所有消息记录类型具有基本相同的结构,映射到上面的数据大小限制。

如果要将数据发送到另一个包含两个以上32位大小的变量的线程,那么事情就变得棘手了。由于可以发送的值的大小限制,您可能无法发送整个记录,但只能发送其地址。为此,您将在发送线程中动态分配数据结构,将地址作为消息参数之一传递,并将接收线程中的相同参数重新解释为具有相同类型的变量的地址,然后使用记录,并释放动态分配的内存结构。

因此,根据您需要发送给事件处理程序的数据量,您可能需要更改tMyMessage记录。这可以使用,但它比必要更困难,因为类型检查不适用于您的事件数据。

我建议稍微改变一下。您知道需要从工作线程传递到GUI线程的数据。只需创建一个排队数据结构,您可以将事件参数数据放入其中,而不是直接使用消息发送它们。使这个队列是线程安全的,即用一个关键部分保护它,这样即使从不同的线程同时尝试,添加或删除队列也是安全的。

要请求新的事件处理,只需将数据添加到队列中即可。仅在将第一个数据元素添加到先前空的队列时,才将消息发布到接收线程。接收线程然后应该接收并处理消息,并继续从队列中弹出数据元素并调用匹配的事件处理程序,直到队列再次为空。为了获得最佳性能,应尽快锁定队列,并且在调用事件处理程序时,应该暂时再次解锁。

答案 2 :(得分: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;

如果你想完全使用,那么请使用PostMessage API。

答案 3 :(得分:0)

检查同步方法的文档。它专为像你这样的任务而设计。

答案 4 :(得分:0)

如果您希望查看(http://www.csinnovations.com/framework_overview.htm),我的框架可以为您执行此操作。