Delphi - Межпотоковая обработка событий - PullRequest
3 голосов
/ 27 августа 2010

У меня есть небольшое клиент-серверное приложение, где сервер отправляет клиенту некоторые сообщения, используя именованные каналы.У клиента есть два потока - основной поток 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;

, который будет выполняться не в принимающем потоке, а в основном потоке пользовательского интерфейса.Я бы особенно хотел, чтобы принимающий поток просто вызвал событие и продолжил выполнение, не дожидаясь возврата метода обработчика события (в основном мне нужно что-то вроде .NET Control.BeginInvoke метод)

Я действительно новичок в этом (я пытался научиться определять пользовательские события всего несколько часов назад.), Поэтому я не знаю, возможно ли это вообще или я делаю что-то не так, так что спасибомного заранее за вашу помощь.

Ответы [ 5 ]

2 голосов
/ 28 августа 2010

У вас уже было несколько ответов, но ни один из них не упомянул тревожную часть вашего вопроса:

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

Обратите внимание, что вы не можете делать все то, что вы можете считать само собой разумеющимся всреда .NET, когда вы используете Delphi или какую-либо другую оболочку для собственной обработки сообщений Windows.Вы можете ожидать, что сможете передавать случайные структуры данных обработчику событий, но это не сработает.Причина заключается в необходимости управления памятью.

В .NET вы можете быть уверены, что структуры данных, на которые больше нет ссылок, будут удалены из-за сборки мусора.В Delphi у вас нет такой свободы действий, вам нужно убедиться, что любой выделенный блок памяти также освобожден правильно.

В Windows получатель сообщения является либо дескриптором окна (a HWND) который вы SendMessage() или PostMessage(), или это поток, к которому вы PostThreadMessage().В обоих случаях сообщение может содержать только два элемента данных, оба из которых имеют ширину машинного слова, первый тип WPARAM, второй тип LPARAM).Вы не можете просто отправить или опубликовать произвольную запись в качестве параметра сообщения.

Все типы записей сообщений, используемые Delphi, имеют в основном одинаковую структуру, которая соответствует приведенному выше ограничению размера данных.

ЕслиВы хотите отправить данные в другой поток, который состоит из более чем двух 32-битных переменных, и тогда все становится сложнее.Из-за ограничений размера значений, которые могут быть отправлены, вы не сможете отправить всю запись, а только ее адрес.Для этого вы должны динамически распределить структуру данных в потоке-отправителе, передать адрес в качестве одного из параметров сообщения и переинтерпретировать тот же параметр в потоке-получателе, что и адрес переменной того же типа, а затем использовать данные взапись и освободите динамически распределяемую структуру памяти.

Таким образом, в зависимости от объема данных, которые необходимо отправить обработчику событий, вам может потребоваться изменить запись tMyMessage.Это можно заставить работать, но это сложнее, чем необходимо, потому что проверка типа недоступна для данных вашего события.

Я бы посоветовал решить это немного по-другому.Вы знаете, какие данные вам нужно передать из рабочих потоков в поток GUI.Просто создайте структуру данных, в которую вы помещаете данные параметров вашего события, вместо того, чтобы отправлять их вместе с сообщением.Сделайте эту очередь поточно-ориентированной, то есть защитите ее критическим разделом, чтобы добавление или удаление из очереди было безопасно даже при попытке одновременно из разных потоков.

Чтобы запросить новую обработку событий, просто добавьте данные втвоя очередьОтправлять сообщение в принимающий поток можно только тогда, когда первый элемент данных добавлен в ранее пустую очередь.Затем принимающий поток должен получить и обработать сообщение и продолжить извлекать элементы данных из очереди и вызывать соответствующие обработчики событий, пока очередь снова не станет пустой.Для лучшей производительности очередь должна быть заблокирована как можно короче, и она определенно должна быть снова временно разблокирована, пока вызывается обработчик события.

2 голосов
/ 27 августа 2010

Вы должны использовать API PostMessage (асинхронный) или SendMessage (синхронный) для отправки сообщения в окно. Вы также можете использовать какую-то «очередь» или использовать фантастическую OmniThreadLibrary, чтобы «сделать это» (настоятельно рекомендуется)

1 голос
/ 27 августа 2010

Объявите личный член

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.

0 голосов
/ 20 февраля 2011

Мой фреймворк может сделать это для вас, если вы хотите проверить это (http://www.csinnovations.com/framework_overview.htm).

0 голосов
/ 27 августа 2010

Проверьте документы для метода синхронизации. Он предназначен для таких задач, как ваша.

...