2010-08-27 4 views
3

У меня есть небольшое клиент-серверное приложение, где сервер отправляет некоторые сообщения клиенту с использованием именованных каналов. Клиент имеет два потока - основной поток графического интерфейса и один «принимающий поток», который продолжает получать сообщения, отправленные сервером через именованный канал. Теперь, когда какое-либо сообщение получено, я хотел бы запустить настраиваемое событие - однако это событие должно обрабатываться не на вызывающем потоке, а в главном потоке графического интерфейса - и я не знаю, как это сделать (и это даже возможно).Delphi - обработка событий с перекрестными потоками

Вот что я до сих пор:

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)

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

ответ

2

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

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

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

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

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

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

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

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

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

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

0

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

+0

Ugh. Синхронизация останавливает все второстепенные потоки и выполняется в контексте основного потока, что означает, что он сильно нарушает назначение нескольких потоков в первую очередь. Есть TONS лучших способов, чем Synchronize. Однако я не голосую за вас, потому что, хотя это ужасный ответ, это технически обоснованный вариант. :-) –

+2

Кто вам сказал этот миф? Синхронизация не блокирует «все» вторичные потоки. Он только блокирует поток, из которого он был вызван, и это происходит из-за его синхронного характера. Но другие потоки продолжают работать. –

+0

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

2

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

+1

+1 для несинхронизации. Пример использования сообщения post/send действительно будет полезен, тем более, что OP cleary указывает на абсолютный новичок ... –

+1

... и Synchronize отлично подходит для начинающих, поскольку он не принимает новичка в глубину обмена сообщениями Windows , –

1

Объявите закрытый член

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; 

Если вы хотите сделать это полностью asyn ch вместо этого используйте PostMessage API.

Смежные вопросы