Перейти к содержанию
Fire Monkey от А до Я
  • 0

Обращение к серверу в потоке (Android)


gonzales

Вопрос

Доброго времени суток!

Решаю следующую задачу, в приложении динамически формируются разные объекты, наследники от одного класса. При формировании объектов заполняется динамический массив этих элементов. Далее я хочу в отдельном потоке для каждого из элементов массива получить его состояние, то есть делаю запрос к серверу. Все это повешено на таймер, каждую секунду должен отрабатываться запрос. Все более менее работает в Windows, а на Андроиде со временем приложение валится. Вот код таймера, для читаемости я удалил куски с различными вариантами E. RootElements - это массив TEssense от которого есть наследники. Функции GetBoardCurrentValue, GetBoardMaxValue - по сути запросы к серверу. 

Подскажите, правильно ли я оформляю работу с потоками для работы на Андроиде?

procedure TForm1.MasterTimerTimer(Sender: TObject);
begin
  TTask.Run(
    procedure
    var
      l, d, a: byte;
      i,j:integer;
      E: TEssence;
      p: Pointer;
      VirtualNode: IXMLNode;
      VirtualElementNode: IXMLNode;
      id: byte;
    begin
      l := Length(Form1.RoomElements);
      for j := 0 to l - 1 do
      begin
        E := Form1.RoomElements[j];

        // Реле
        if E is TRele then
        begin
          d := (E as TRele).Device_ID;
          a := (E as TRele).Device_Adress;
          if Form1.GetBoardCurrentValue(d, a) = true then
          begin
            TThread.Synchronize(nil,
              procedure
              begin
                (E as TRele).ReleSwitch.IsChecked := Form1.device[d].Board[a].CurrentValue.ToBoolean;
              end);
          end;
          // (E as TRele).ReleOnTimer(E)
        end

        // Диммер
        else if E is TDimmer then
        begin
          d := (E as TDimmer).Device_ID;
          a := (E as TDimmer).Device_Adress;
          if Form1.GetBoardMaxValue(d, a) = true then
          begin
            TThread.Synchronize(nil,
              procedure
              begin
                if (Form1.device[d].Board[a].Type_ID = TType.Светодиод) or
                  (Form1.device[d].Board[a].Type_ID = TType.Диммер220) then
                begin
                  (E as TDimmer).DimmerValue.Text := (Form1.device[d].Board[a].MaxValue).ToString;
                end;
              end);
          end;
          // (E as TDimmer).DimmerOnTimer(E)
        end

        // Таймер
        else if E is TSTimer then
        begin
          id := (E as TSTimer).STimerIndex;
          Form1.FillHTTPRequest(0, 0, HTTP_GET_TIMER_INFO, id);
          if Form1.AnswerIsComming = HTTP_GET_TIMER_INFO then
          begin
            TThread.Synchronize(nil,
              procedure
              begin
                if Form1.HTTPAnswer.Data1 = 0 then
                  (E as TSTimer).Interval.Text := 'OFF'
                else
                  (E as TSTimer).Interval.Text := 'ON'
              end);
          end;
          // (E as TSTimer).STimerOnTimer(E);
        end;
      end;
    end);
end;

  

Ссылка на комментарий

Рекомендуемые сообщения

  • 0

Еще есть подозрение, что проблема кроется в том, что из потока идет обращение к функции посылки и приема сообщений

Вот она

procedure TForm1.SendHTTPMessage;
    var
      t_out, t_in: TIdBytes;
      Size: TSizeF;
      localAnswer: byte;
      localicon: byte;
      localserver: boolean;
    begin
      try
        Size.cx := 16;
        Size.cy := 16;
        if Client1.Connected = false then
          Client1.Connect;

        if Client1.Connected = true then
        begin
          localicon := 15;
          localserver := true;
          localAnswer := 0;
          SetLength(t_out, SizeOf(HTTPRequest));
          Move(HTTPRequest, t_out[0], SizeOf(HTTPRequest));
          Client1.Socket.WriteBufferOpen;
          Client1.Socket.Write(t_out, 13);
          Client1.Socket.WriteBufferFlush;
          Client1.Socket.WriteBufferClose;
          SetLength(t_in, SizeOf(THTTPAnswer));

          try
            Client1.Socket.ReadBytes(t_in, SizeOf(THTTPAnswer), false);
            Move(t_in[0], HTTPAnswer, Length(t_in));
            localAnswer := HTTPAnswer.Command;
            Client1.Disconnect;
          except
            localicon := 7;
            localserver := false;
            localAnswer := 0;
            Form1.Client1.Disconnect;
          end;
        end
        else
        begin
          Client1.Disconnect;
          localicon := 7;
          localserver := false;
          localAnswer := 0;
        end;
      except
        on E: Exception do
        begin
          // showmessage('Error: ' + E.Message);
          Client1.Disconnect;
          localicon := 7;
          localserver := false;
          localAnswer := 0;
        end;
      end;

          Form1.ConnectImage.Bitmap := Form1.IconList.Bitmap(Size, localicon);
          AnswerIsComming := localAnswer;
          Form1.ServerOK := localserver;


end;

 

Ссылка на комментарий
  • 0

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

Опишу более подробно.

Есть объекты, у них есть состояние, о нем знает сервер. Соответственно клиентов может быть несколько и у всех должно синхронно обновляться состояние при изменении его на сервере. Сервер - это МК со своими прибамбасами, но сейчас не об этом. Клиенты - приложение для IOS и Android. Задача - получать данные с сервера о состоянии активных в данный момент объектах, желательно, чтобы основной интерфейс не тормозил в моменты получения информации.

Ссылка на комментарий
  • 0

Попробуйте чуть изменить архитектуру. В одном потоке получать информацию, с заданной периодичностью, в какую нибудь структуру. А в другом потоке опрашивать уже заполненную структуру на предмет измененного состояния объектов.

Ссылка на комментарий
  • 0

На первый взгляд можно заметить следующее:

1. Обращение к форме из потока без синхронизации

l := Length(Form1.RoomElements);
E := Form1.RoomElements[j];

Так делать нельзя. SendHTTPMessage - тоже самое.

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

3. У вас сколько объектов по которому цикл проходит каждую секунду? И для каждого объекта вызывается Get запрос на веб сервер? Бедный сервер. Данные нужно получать сразу для всех объектов, и уже в клиенте разбирать, что вам нужно, а что нет. А иначе ваши клиенты положат вам ваш же сервер рано или поздно.

4. Уже несколько версий существует TNetHttpClient и THTTPClient, которые могут асинхронно отправлять запросы на веб сервер, а значит поток вообще создавать не нужно.

Ссылка на комментарий
  • 0

Спасибо Maximus, учту. 

Несколько уточняющих вопросов

1. Любое обращение к форме нужно проводить с синхронизацией? Я думал это только визуальных компонентов касается, которые должны в главном потоке отрисовываться.

2. С таймером - хорошая мысль, попробую, вдруг действительно не успевает отработать, это объяснило бы глюки на андроиде.

3. Объектов до 10 и по каждому запрос, только запрос не HTTP, так как у меня не web-сервер, а это просто пакет байт, на который сервер отвечает таким же пакетом. Я уже думаю над тем, чтобы передавать всю информацию в одном пакете, но для этого придется полностью переколбасить сервер, а сейчас пока на это нет времени. А нельзя TCPClient заставить работать в асинхронном режиме? 

Ссылка на комментарий
  • 0
1 час назад, gonzales сказал:

1. Любое обращение к форме нужно проводить с синхронизацией? Я думал это только визуальных компонентов касается, которые должны в главном потоке отрисовываться.

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

Но вот это же точно визуальный элемент?

Form1.ConnectImage.Bitmap := Form1.IconList.Bitmap(Size, localicon);

 

1 час назад, gonzales сказал:

3. Объектов до 10 и по каждому запрос, только запрос не HTTP, так как у меня не web-сервер, а это просто пакет байт, на который сервер отвечает таким же пакетом. Я уже думаю над тем, чтобы передавать всю информацию в одном пакете, но для этого придется полностью переколбасить сервер, а сейчас пока на это нет времени. А нельзя TCPClient заставить работать в асинхронном режиме? 

Тогда это уже менее страшно, я почему-то подумал, что обращение идёт через HTTP. TCPClient не имеет такой возможности, только самостоятельно потоки делать.

Ссылка на комментарий
  • 0

асинхронный режим на самом деле - это запуск чего-то в потоке и придумывание архитектуры, как будете результаты забирать

либо в обработчике окончания потока, либо через синхронизацию не заканчивая поток

Ссылка на комментарий
  • 0
23 часа назад, Maximus сказал:

Но вот это же точно визуальный элемент?

Разумеется. Я не подумал, что вызывая SendHTTPMessage в потоке я также должен синхронизировать результаты. Пока не удается завернуть SendHTTPMessage в поток, обмен данными не идет. Буду искать ошибки. Во всяком случае большое спасибо за наводки

Ссылка на комментарий
  • 0
1 час назад, krapotkin сказал:

асинхронный режим на самом деле - это запуск чего-то в потоке и придумывание архитектуры, как будете результаты забирать

либо в обработчике окончания потока, либо через синхронизацию не заканчивая поток

Для асинхронного режима в Windows  есть WaitForSingleObject( Event, INFINITE ) , что аналогичное можно использовать в много платформенных приложениях?

Ссылка на комментарий
  • 0
6 минут назад, t0rn сказал:

У меня все сделано на MQTT. МК arduino uno на данный момент вещает на три androida и две винды. Все четко и синхронно.

Это прекрасно)))

А как реализован MQTT в firemonkey? А брокером сама ардуино является?

Ссылка на комментарий
  • 0

Есть у tms компонент, с ним все работает нормально, но он платный. Я использовал вот эту библиотеку https://github.com/join2017/dmqtt . Брокер на данный момент вот этот использую https://cloudmqtt.com/ . Arduino туда шлет и от туда принимает. Но сейчас свой сервак поднимаю.

Ссылка на комментарий
  • 0
1 час назад, krapotkin сказал:

это как бы стоять и ждать пока
можно с тем же успехом 
repeat ... until Done;
поток блокируется.

Несколько не так, WaitForSingleObject поток засыпает и ждет тот же Event что хорошо для реализации асинхронного режима, repeat ... until Done поток не спит, нагружая процессор не нужной работой.

Ссылка на комментарий
  • 0
Цитата

Для асинхронного режима в Windows  есть WaitForSingleObject( Event, INFINITE )

Не очень понял, чем это может помочь! В общем процедуру, которая в потоке запрашивает у сервера информацию и обновляет состояние компонента я написал, но теперь есть другая засада. Эта процедура срабатывает по таймеру в 1 сек, соответственно работает в своем потоке. Но иногда проявляется следующий баг, пользователь принудительно меняет состояние объекта (при этом он перерисовывается), и тут же перерисовывается обратно по таймеру (так как информация о смене состояния на сервер еще не попала) и только на следующем такте (обращении по таймеру к серверу) объект перерисуется в правильное состояние. Вопрос, могу ли я каким-то образом в момент нажатия пользователем на объект остановить выполнение задачи в потоке?

Надеюсь что-то понятно из моего объяснения))

Ссылка на комментарий
  • 0
2 часа назад, gonzales сказал:

Не очень понял, чем это может помочь!

В windows это позволяет запустить поток когда вам нужно, а не по таймеру.

Изменено пользователем GASCHE
Ссылка на комментарий
  • 0
7 часов назад, gonzales сказал:

Не очень понял, чем это может помочь! В общем процедуру, которая в потоке запрашивает у сервера информацию и обновляет состояние компонента я написал, но теперь есть другая засада. Эта процедура срабатывает по таймеру в 1 сек, соответственно работает в своем потоке. Но иногда проявляется следующий баг, пользователь принудительно меняет состояние объекта (при этом он перерисовывается), и тут же перерисовывается обратно по таймеру (так как информация о смене состояния на сервер еще не попала) и только на следующем такте (обращении по таймеру к серверу) объект перерисуется в правильное состояние. Вопрос, могу ли я каким-то образом в момент нажатия пользователем на объект остановить выполнение задачи в потоке?

Прям немедленно остановить нельзя, но можно вызвать метод Cancel

var
	Task   : ITask;
...
Task := TTask.Run(...);
...
Task.Cancel; //когда нужно остановить

А внутри таска сделать проверки, что если выполняется

Task.Status = TTaskStatus.Canceled

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

Ссылка на комментарий
  • 0
13 часа назад, Maximus сказал:

Прям немедленно остановить нельзя, но можно вызвать метод Cancel

Спасибо большое, как раз то что нужно!!! Реализовал, все работает

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить на вопрос...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...