TIdCmdTCPServer и синхронизация данных с основным потоком [аномалия?] - PullRequest
0 голосов
/ 24 декабря 2018

Ситуация выглядит следующим образом.Внешнее приложение client.exe отправляет на сервер команду MONITOR_ENCODING каждые ~ 250 мс.В серверном приложении я использую IdCmdTCPServer1BeforeCommandHandler для чтения отправленной команды от клиента.Я копирую полученную команду ( AData string) в глобальную переменную command и затем показываю ее в memo1.Тем временем я постоянно запускаю TSupervisorThread, который копирует команду глобальной переменной в локальную переменную Copiedcommand, а затем присваивает новый текст переменной команды.Является ли это ожидаемым поведением, что время от времени вместо текста MONITOR_ENCODING я получаю RESET?

enter image description here

procedure TForm1.IdCmdTCPServer1BeforeCommandHandler(ASender: TIdCmdTCPServer; var AData: String; AContext: TIdContext);
begin

  command:=AData;  //command is a global variable
  form1.Memo1.Lines.Add(IntToStr(form1.Memo1.Lines.Count+1)+'|'+IntToStr(GetTickCount)+'|'+command);

end;

procedure TSupervisorThread.CopyGlobalVariables;
begin

  CopiedCommand:=command; //Copiedcommand declared in TSupervisorThread
  command:='RESET'; 

end;

procedure TSupervisorThread.Execute;
begin

  while Terminated=false do
  begin
    Synchronize(CopyGlobalVariables);
    sleep(250);
  end;

end;

1 Ответ

0 голосов
/ 24 декабря 2018

FYI, событие OnBeforeCommandHandler не является правильным местом для чтения команд с TIdCmdTCPServer.Вы должны добавить запись для каждой команды в свою коллекцию CommandHandlers, а затем назначить обработчик OnCommand для каждой записи.Событие OnBeforeCommandHandler запускается до того, как TIdCmdTCPServer проанализирует полученную команду.Это нормально для целей ведения журнала, просто убедитесь, что вы не используете его для управления логикой обработки.Оставьте это отдельным OnCommand событиям.

Но в любом случае чтение команд выполняется в рабочем потоке.Вы не синхронизируете с основным потоком пользовательского интерфейса при добавлении полученных команд в свой пользовательский интерфейс.Вы ДОЛЖНЫ синхронизироваться.Есть много способов сделать это - TThread.Synchronize(), TThread.Queue(), TIdSync, TIdNotify, (Send|Post)Message() и т. Д., Просто назвать несколько.

Что еще более важно, у вас есть 2 потока (или больше, в зависимости от того, сколько клиентов подключено одновременно), которые борются за одну и ту же глобальную переменную, вообще не синхронизируя доступ к ней.Вам нужна блокировка вокруг переменной, например TCriticalSection или TMutex, или использование класса TIdThreadSafeString в Indy.

Но это не решит условия гонки, которые есть в вашем коде.Между временем, когда ваш OnBeforeCommandHandler назначает новое значение для command, и временем, когда оно читает command назад, чтобы добавить его в пользовательский интерфейс, ваш TSupervisorThread может изменять command.Это именно то, что вы видите, происходит.Это не аномалия в Indy, это проблема синхронизации в вашем коде.

Самое простое решение этого состояния гонки - просто добавить AData в ваш интерфейс вместо command.Таким образом, не имеет значения, если TSupervisorThread изменит command, ваш пользовательский интерфейс его не увидит.

Но почему вы вообще используете глобальную переменную command?Какова его настоящая цель?Вы пытаетесь заставить внешний поток изменить команды, которые TIdCmdTCPServer анализирует?Вы не контролируете, какие клиенты получают парсинг реальных команд, а какие получают фальшивые команды.Почему вы это делаете?

Кроме того, если TSupervisorThread выполнять 99% своей работы в основном потоке пользовательского интерфейса, это плохое использование рабочего потока, вы можете просто использовать TTimer впользовательский интерфейс вместоВ противном случае вам нужно лучше координировать свои потоки, например, используя TEvent объекты для сигнализации, когда назначено command, и когда оно сброшено.

Я думаю, вам нужно переосмыслить свой дизайн.

...