简体   繁体   English

Delphi - 跨线程事件处理

[英]Delphi - Cross-thread event handling

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. 客户端有两个线程 - 主GUI线程和一个“接收线程”,它不断接收服务器通过命名管道发送的消息。 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). 现在每当收到一些消息时,我都想触发一个自定义事件 - 但是,该事件应该不是在调用线程上处理,而是在主GUI线程上处理 - 我不知道该怎么做(以及是否它甚至可能)。

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 现在,我希望能够创建事件处理程序作为TForm1类的成员

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. 这不会在接收线程中执行,而是在主UI线程中执行。 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) 我特别喜欢接收线程只是触发事件并继续执行而不等待事件处理程序方法的返回(基本上我需要类似.NET Control.BeginInvoke方法)

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. 我是初学者(我试着学习如何在几个小时之前定义自定义事件。),所以我不知道它是否可能或者我做错了什么,所以非常感谢提前做好准备你的帮助。

You should use PostMessage (asynch) or SendMessage (synch) API to send a message to' a window. 您应该使用PostMessage(asynch)或SendMessage(synch)API向“窗口”发送消息。 You could use also some kind of "queue" or use the fantastic OmniThreadLibrary to' do this (highly recomended) 您也可以使用某种“队列”或使用梦幻般的OmniThreadLibrary'做到这一点(高度推荐)

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. 请注意,当您使用Delphi或其他一些包装器进行本机Windows消息处理时,您无法完成在.NET环境中可能认为理所当然的所有事情。 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. 在.NET中,您可以确保不再从任何地方引用的数据结构将被垃圾回收处理掉。 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. 在Delphi中,您没有相同的余地,您需要确保已正确释放任何已分配的内存块。

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. 在Windows中,消息接收器可以是SendMessage()PostMessage()的窗口句柄( HWND ),也可以是PostThreadMessage()的线程。 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 ). 在这两种情况下,消息只能携带两个数据成员,这两个数据成员都是机器字宽,第一个是WPARAM类型,第二个是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. Delphi使用的所有消息记录类型具有基本相同的结构,它映射到上面的数据大小限制。

If you want to send data to another thread which consists of more than two 32 bit sized variables, then things get tricky. 如果你想将数据发送到另一个包含两个以上32位大小的变量的线程,那么事情就变得棘手了。 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. 因此,根据您需要发送到事件处理程序的数据量,您可能需要更改tMyMessage记录。 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. 您知道需要从工作线程传递到GUI线程的数据。 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, ie 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. 为了获得最佳性能,应尽快锁定队列,并且在调用事件处理程序时,应该暂时再次解锁。

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. 如果你想完全使用它,那么请使用PostMessage API。

Check docs for Synchronize method. 检查Synchronize方法的文档。 It's designed for tasks like yours. 它专为像您这样的任务而设计。

如果你想查看它,我的框架可以为你做这个( http://www.csinnovations.com/framework_overview.htm )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM