views:

166

answers:

4

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.

A: 

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

Eugene Mayevski 'EldoS Corp
Ugh. Synchronize halts all secondary threads and executes in the context of the main thread, meaning it defeats much of the purpose of having multiple threads in the first place. There are TONS of better ways than Synchronize. I'm not voting you down, though, because even though it's a terrible answer it's technically a valid one. :-)
Ken White
Who told you that myth? Synchronize doesn't block "all" secondary threads. It only blocks the thread from which it has been called, and this happens due to it's synchronous nature. But other threads keep running.
Eugene Mayevski 'EldoS Corp
Yes only the calling thread gets blocked. It is not the best mechanism but if you are aware how it works you should be ok.
Runner
Using `Synchronize()` means making sure that the event handler has finished at the time the call returns, i.e. ignoring the following part of the question: "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".
mghie
+2  A: 

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)

Daniele Teti
+1 for not using synchronize. An example using post/send message would really be helpful, especially as the OP cleary indicates being an absolute beginner...
Marjan Venema
... and Synchronize is perfect for beginner, cause it doesn't take the beginner into the depth of Windows messaging.
Eugene Mayevski 'EldoS Corp
A: 

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.

PeterS
+1  A: 

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.

mghie