Multithreading Delphi-跨线程事件处理
我有一个小型的客户机-服务器应用程序,其中服务器使用命名管道向客户机发送一些消息。客户机有两个线程—主GUI线程和一个“接收线程”,它不断接收服务器通过命名管道发送的消息。现在,每当收到某条消息时,我都想触发一个自定义事件——但是,该事件不应该在调用线程上处理,而应该在主GUI线程上处理——我不知道该怎么做(甚至不知道是否可能) 以下是我目前掌握的情况:Multithreading Delphi-跨线程事件处理,multithreading,delphi,delphi-6,Multithreading,Delphi,Delphi 6,我有一个小型的客户机-服务器应用程序,其中服务器使用命名管道向客户机发送一些消息。客户机有两个线程—主GUI线程和一个“接收线程”,它不断接收服务器通过命名管道发送的消息。现在,每当收到某条消息时,我都想触发一个自定义事件——但是,该事件不应该在调用线程上处理,而应该在主GUI线程上处理——我不知道该怎么做(甚至不知道是否可能) 以下是我目前掌握的情况: tMyMessage = record mode: byte; //...some other fields... end;
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方法的东西)
我真的是一个初学者(几小时前我就试着学习如何定义自定义事件),所以我甚至不知道这是否可能,或者我是否做了错事,所以提前感谢您的帮助。检查文档以了解同步方法。它是为像您这样的任务设计的。您应该使用PostMessage(异步)或SendMessage(同步)API向“窗口”发送消息。您还可以使用某种“队列”或使用奇妙的OmniThreadLibrary来“执行此操作(高度推荐)声明私有成员”
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。您已经有了一些答案,但没有一个提到问题中令人不安的部分:
tMyMessage = record
mode: byte;
//...some other fields...
end;
请注意,在.NET环境中,当您使用Delphi或其他包装器进行本机Windows消息处理时,您无法完成所有您认为理所当然的事情。您可能希望能够将随机数据结构传递给事件处理程序,但这是行不通的。原因是需要内存管理
在.NET中,您可以确保不再从任何地方引用的数据结构将被垃圾收集处理掉。在Delphi中,您没有同样的余地,您需要确保任何分配的内存块也被正确释放
在Windows中,消息接收器是一个窗口句柄(一个HWND
),可以SendMessage()
或PostMessage()
发送给它,或者是一个线程,可以PostThreadMessage()
发送给它。在这两种情况下,消息只能携带两个数据成员,它们都是机器字宽度,第一个是类型WPARAM
,第二个是类型LPARAM
)。您不能简单地将任何随机记录作为消息参数发送或发布
Delphi使用的所有消息记录类型都具有基本相同的结构,这映射到上面的数据大小限制
如果您想将数据发送到另一个包含两个以上32位大小变量的线程,那么事情就会变得棘手。由于可以发送的值的大小限制,您可能无法发送整个记录,而只能发送其地址。要做到这一点,您需要在发送线程中动态分配数据结构,将地址作为消息参数之一传递,并将接收线程中的相同参数重新解释为具有相同类型的变量的地址,然后使用记录中的数据,并释放动态分配的内存结构
因此,根据需要发送到事件处理程序的数据量,您可能需要更改tMyMessage
记录。这是可以实现的,但它比必要的更困难,因为类型检查不适用于事件数据
我建议以不同的方式处理这个问题。您知道需要将哪些数据从工作线程传递到GUI线程。只需创建一个排队数据结构,将事件参数数据放入其中,而不是直接将其与消息一起发送。使此队列线程安全,即使用关键部分对其进行保护,以便即使从不同线程同时尝试添加或从队列中删除,也可以安全地进行添加或删除
要请求新的事件处理,只需将数据添加到队列中。仅当第一个数据元素添加到以前的空队列时,才向接收线程发布消息。然后,接收线程应该接收并处理消息,并继续从队列中弹出数据元素并调用匹配的事件处理程序,直到队列再次为空。为获得最佳性能,应尽快锁定队列,并且在调用事件处理程序时,一定要暂时再次解锁队列。如果您希望签出队列(),我的框架可以为您执行此操作。。Synchronize停止所有次线程并在主线程的上下文中执行,这意味着它首先破坏了拥有多个线程的目的。有很多比同步更好的方法。不过,我不会投你反对票,因为尽管这是一个糟糕的答案,但从技术上讲,这是一个有效的答案。:-)谁告诉你那个神话的?同步不会阻止“所有”次线程。它只会阻塞调用它的线程,这是由于它的同步性质。但是其他线程仍在运行。是的,只有调用线程被阻塞。这不是最好的机制,但如果您知道它是如何工作的,您应该可以。使用
Synchronize()
意味着确保事件处理程序在调用返回时已完成,即忽略问题的以下部分:“我特别希望接收线程只触发事件并继续执行,而不等待事件处理程序方法的返回”。它会阻止任何希望调用Synchronize
的其他线程。+1因为不使用Synchronize。使用post/send message的示例将非常有用,特别是