Delphi - 跨线程事件处理

发布于 2024-09-15 23:59:41 字数 1703 浏览 6 评论 0原文

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

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

I have a small client-server application, where server sends some messages to the client using named pipes. The client has two threads - main GUI thread and one "receiving thread", that keeps receiving the messages sent by server via the named pipe. Now whenever some message is received, I'd like to fire a custom event - however, that event should be handled not on the calling thread, but on the main GUI thread - and I don't know how to do it (and whether it's even possible).

Here's what I have so far:

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;

Now I'd like to be able to create event handler as member of TForm1 class, for example

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

that wouldn't be executed in the receiving thread, but in main UI thread. I'd especially like the receiving thread to just fire the event and continue in the execution without waiting for the return of event handler method (basically I'd need something like .NET Control.BeginInvoke method)

I'm really beginner at this (I tried to learn how to define custom events just few hours ago.), so I don't know if it's even possible or if I'm doing something wrong, so thanks a lot in advance for your help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

不如归去 2024-09-22 23:59:41

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

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

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

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

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

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

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

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

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

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

You've had some answers already, but none of them mentioned the troubling part of your question:

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

Please take note that you can't do all the things you may take for granted in a .NET environment when you use Delphi or some other wrapper for native Windows message handling. You may expect to be able to pass random data structures to an event handler, but that won't work. The reason is the need for memory management.

In .NET you can be sure that data structures that are no longer referenced from anywhere will be disposed off by the garbage collection. In Delphi you don't have the same kind of leeway, you will need to make sure that any allocated block of memory is also freed correctly.

In Windows a message receiver is either a window handle (a HWND) which you SendMessage() or PostMessage() to, or it is a thread which you PostThreadMessage() to. In both cases a message can carry only two data members, which are both of machine word width, the first of type WPARAM, the second of type LPARAM). You can not simply send or post any random record as a message parameter.

All the message record types Delphi uses have basically the same structure, which maps to the data size limitation above.

If you want to send data to another thread which consists of more than two 32 bit sized variables, then things get tricky. Due to the size limits of the values that can be sent you may not be able to send the whole record, but only its address. To do that you would dynamically allocate a data structure in the sending thread, pass the address as one of the message parameters, and reinterpret the same parameter in the receiving thread as the address of a variable with the same type, then consume the data in the record, and free the dynamically allocated memory structure.

So depending on the amount of data you need to send to your event handler you may need to change your tMyMessage record. This can be made to work, but it's more difficult than necessary because type checking is not available for your event data.

I'd suggest to tackle this a bit differently. You know what data you need to pass from the worker threads to the GUI thread. Simply create a queueing data structure that you put your event parameter data into instead of sending them with the message directly. Make this queue thread-safe, i.e. protect it with a critical section so that adding or removing from the queue is safe even when attempted simultaneously from different threads.

To request a new event handling, simply add the data to your queue. Only post a message to the receiving thread when the first data element is added to a previously empty queue. The receiving thread should then receive and process the message, and continue to pop data elements from the queue and call the matching event handlers until the queue is empty again. For best performance the queue should be locked as shortly as possible, and it should definitely be unlocked again temporarily while the event handler is called.

殤城〤 2024-09-22 23:59:41

您应该使用 PostMessage(异步)或 SendMessage(同步)API 将消息发送到窗口。您还可以使用某种“队列”或使用出色的 OmniThreadLibrary 来执行此操作(强烈推荐)

You should use PostMessage (asynch) or SendMessage (synch) API to send a message to' a window. You could use also some kind of "queue" or use the fantastic OmniThreadLibrary to' do this (highly recomended)

楠木可依 2024-09-22 23:59:41

声明一个私有成员

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。

Declare a private member

FRecievedMessage: TMyMEssage

And a protected procedure

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

And change the code in the loop to

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;

If you want to do it completely asynch use PostMessage API instead.

北风几吹夏 2024-09-22 23:59:41

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

Check docs for Synchronize method. It's designed for tasks like yours.

埖埖迣鎅 2024-09-22 23:59:41

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

My framework does can do this for you if you wish to check it out (http://www.csinnovations.com/framework_overview.htm).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文