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方法之类的东西)

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

4

5 回答 5

2

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

于 2010-08-27T17:32:39.377 回答
2

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

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

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

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

在 Windows 中,消息接收器要么是你或你要的窗口句柄 (a HWND) ,要么是你要的线程。在这两种情况下,一条消息只能携带两个数据成员,它们都是机器字宽,第一个是 type ,第二个是 type )。您不能简单地发送或发布任何随机记录作为消息参数。SendMessage()PostMessage()PostThreadMessage()WPARAMLPARAM

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

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

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

我建议以不同的方式解决这个问题。您知道需要从工作线程传递到 GUI 线程的数据。只需创建一个队列数据结构,将事件参数数据放入其中,而不是直接将它们与消息一起发送。使这个队列线程安全,即用一个临界区保护它,这样即使从不同的线程同时尝试,从队列中添加或删除也是安全的。

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

于 2010-08-27T23:43:04.120 回答
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。

于 2010-08-27T17:45:05.967 回答
0

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

于 2010-08-27T16:52:02.190 回答
0

如果您想检查一下(http://www.csinnovations.com/framework_overview.htm),我的框架确实可以为您做到这一点。

于 2011-02-20T10:03:54.083 回答