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

[Android] обмен сообщениями между потоками и формой


ddr 2

Вопрос

Привет

Под WIN32/64:

 - сообщение из формы в поток отправлял PostThreadMessage(myThread.ThreadID, WM_user+1, nativeuint(@data1),nativeuint(@data2));

- сообщение из потока в форму SendMessage(FormHandle,WM_user+2,nativeuint(@data1),nativeuint(@data2)); 

В Android такого нет. Подскажите, как организовать обмен сообщениями между формой и потоками в Android?

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

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

  • 0

Не знал про TThreadedQueue. Полезный функционал, спасибо!

Полагаю, что отсылку сообщения из потока в форму можно/нужно делать через TMessageManager. Написал небольшой тест(ниже по тексту- поток посылает сообщения, а форма принимает их и отображает ). Увы, но тест работает не корректно. При запуске под Windows, в процедуре обработке принятого сообщения выскакивает исключение,  где-то в районе label1.Text:=stringrecive; . Если в процедуре TForm2._ReciveMessage закоментировать строчки label1.Text:=stringrecive; и label2.Text:=stringrecive ,то оставшиеся добавление  строки в memo работает, но явно "не здорово" подмаргивает. При запуске под Android, изменение текста в метках работает, но memo так же дергается и моргает. Применение TThreadedQueue никак не влияет на результат и в тексте закоментировано.

Прошу помощи, в чем ошибка?

unit Unit2;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,System.Messaging,
  FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, FMX.StdCtrls,System.Generics.Collections;

type
  tmythread=class(TThread)
  public
  constructor Create;
  protected
  packetnomber:integer;
  procedure Execute; override;
end;

  TForm2 = class(TForm)
    Memo1: TMemo;
    Label1: TLabel;
    Label2: TLabel;
    procedure FormCreate(Sender: TObject);
  private
  mythread:tmythread;
  procedure _ReciveMessage(const Sender: TObject; const M: TMessage);
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;
//  qqueue: TThreadedQueue<string>;
implementation

constructor tmythread.Create;
begin
 packetnomber:=0;
 FreeOnTerminate := true;
 inherited create(false);
end;

procedure tmythread.Execute;
var strmessage:string; MessageManager: TMessageManager;_Message: TMessage;
begin
 while (not Terminated) do
 begin
 inc(packetnomber);
 strmessage:='Пакет№ '+inttostr(packetnomber)+' значение X='+inttostr(random(1000))+
 ' значение Y='+inttostr(random(1000))+' значение Z='+inttostr(random(1000));
 //qqueue.PushItem(strmessage);
 _Message:=TMessage<String>.Create(strmessage);
 MessageManager := TMessageManager.DefaultManager;
 MessageManager.SendMessage(self, _Message, true);
 sleep(random(800));
 end;
end;

{$R *.fmx}

procedure TForm2._ReciveMessage(const Sender: TObject; const M: TMessage);
var stringrecive:string;
begin
 stringrecive:=(M as TMessage<String>).Value;
 //stringrecive:=qqueue.PopItem;
 label1.Text:=stringrecive;
 label2.Text:=stringrecive;
  memo1.Lines.Add(stringrecive);memo1.GoToTextEnd;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  SubscriptionId: Integer;
  MessageManager: TMessageManager;
begin
//  qqueue := TThreadedQueue<string>.Create(5, 1000);
  memo1.StyledSettings:=[]  ;
  memo1.TextSettings.Font.Size:=8;
  label1.StyledSettings:=[];
  label1.TextSettings.Font.Size:=8;
  label2.StyledSettings:=[];
  label2.TextSettings.Font.Size:=8;

  mythread:=tmythread.Create;
  MessageManager := TMessageManager.DefaultManager;
  SubscriptionId := MessageManager.SubscribeToMessage(TMessage<String>,self._ReciveMessage);
end;
end.

 

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

в чем ошибка?

Поставьте бряк внутри метода

_ReciveMessage

и удивитесь - в каком потоке он вызывается.

Ну или напишите
if TThread.Current.ThreadID<>MainThreadID then // мог слегка ошибиться с наименованиями переменных - пишу по памяти, но вроде это они и есть.
  Алярма!!! (тут что-нибудь сигнализирующее что всё неправильно).

Включая режим буквоедства: смените названия на ReceiveMessage и PackedNumber. Глаз цепляется :)

Ссылка на комментарий
  • 0
В 04.02.2019 в 17:09, kami сказал:

и удивитесь - в каком потоке он вызывается.

Поставил бряк, посмотрел стек, да Вы правы, вызов идет из потока mythread. 

Я хотел реализовать "PostMessage" из потока в главный поток, которого FMX нет. Нашёл подсказку

Цитата

TThread.Queue заменяет PostMessage, если вызов идет не из главного потока.

 
Переделал.Все работает и вызов ReciveMessage происходит из главного потока(MainThread). Правильно ли я понимаю, что в подобной реализации "PostMessage"(речь не про данный код, 
а про сам подход) нужно учитывать и контролировать, что бы поток не завершился раньше, чем все "сообщения" будут обработаны основным потоком? 

Буду рад услышать, о других возможных реализация "PostMessage"

 

unit Unit2;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,System.Messaging,
  FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, FMX.StdCtrls,System.Generics.Collections;

type
  tmythread=class(TThread)
  public
  constructor Create;
  protected
  PackedNumber:integer;
  procedure PostMessage;
  procedure Execute; override;
end;

  TForm2 = class(TForm)
    Memo1: TMemo;
    Label1: TLabel;
    Label2: TLabel;
    procedure FormCreate(Sender: TObject);
  private
  mythread:tmythread;
    { Private declarations }
  public
  procedure ReciveMessage;
    { Public declarations }
  end;

var
  Form2: TForm2;
  qqueue: TThreadedQueue<string>;
implementation

constructor tmythread.Create;
begin
 PackedNumber:=0;
 FreeOnTerminate := true;
 inherited create(false);
end;

procedure tmythread.Execute;
var strmessage:string;
begin
 while (not Terminated) do
 begin
 inc(PackedNumber);
 strmessage:='Пакет№ '+inttostr(PackedNumber)+' значение X='+inttostr(random(1000))+
 ' значение Y='+inttostr(random(1000))+' значение Z='+inttostr(random(1000));
 qqueue.PushItem(strmessage);
 Tthread.Queue(nil,PostMessage);
 sleep(random(800));
 end;
end;

{$R *.fmx}

procedure TForm2.ReciveMessage;
var stringrecive:string;
begin
 stringrecive:=qqueue.PopItem;
 label1.Text:=stringrecive;
 label2.Text:=stringrecive;
  memo1.Lines.Add(stringrecive);memo1.GoToTextEnd;
end;

procedure tmythread.PostMessage;
begin
  Form2.ReciveMessage;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  qqueue := TThreadedQueue<string>.Create(5, 1000);
  memo1.StyledSettings:=[]  ;
  memo1.TextSettings.Font.Size:=8;
  label1.StyledSettings:=[];
  label1.TextSettings.Font.Size:=8;
  label2.StyledSettings:=[];
  label2.TextSettings.Font.Size:=8;
  mythread:=tmythread.Create;
end;
end.

 

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

На основе вашего кода накидал свой вариант.

Главная форма создает 2 очереди: FMyQueueIn - очередь из потока в форму, и FMyQueueOut - очередь из формы в поток. Тип очередей сделал вот такой: 

  TMyMessage = record
    StringMessage : String;
    PackedNumber : Integer;
    X : Integer;
    Y : Integer;
    Z : Integer;
  end;

Вдруг вам понадобятся данные в виде цифр, а не в виде строки.

Конструктор потока получает в качестве параметров эти два потока (но поменянные местами  - чтоб в контексте потока они соотвествовали своим названиям).

Так же добавил по нажатию кнопки отправку текстового  сообщения из формы в поток. Поток получает его, добавляет в начало фразу 'Из главного потока получено: '  и возвращает в форму с очередными данными.

Код и архив проекта прилагаю:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  Generics.Collections,
  System.Generics.Collections, FMX.Controls.Presentation, FMX.ScrollBox,
  FMX.Memo, FMX.StdCtrls;

type

  TMyMessage = record
    StringMessage : String; //Я вынужден был добавить еще строку, иначе в Rio косяк https://quality.embarcadero.com/browse/RSP-21806
    PackedNumber : Integer;
    X : Integer;
    Y : Integer;
    Z : Integer;
  end;

  TMyQueue = TThreadedQueue<TMyMessage>;

  TMyThread = class(TThread)
  protected
    FMyQueueIn : TMyQueue;
    FMyQueueOut : TMyQueue;
    FPackedNumber : Integer;
    procedure Execute; override;
  public
    constructor Create(AMyQueueIn, AMyQueueOut : TThreadedQueue<TMyMessage>);
  end;

  TForm1 = class(TForm)
    Memo: TMemo;
    ToolBar1: TToolBar;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    FMyQueueIn: TMyQueue;
    FMyQueueOut: TMyQueue;
    FMyThread : TMyThread;
    procedure OnIdle(Sender: TObject; var Done: Boolean);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

constructor TMyThread.Create(AMyQueueIn, AMyQueueOut : TMyQueue);
begin
  FMyQueueIn:=AMyQueueIn;
  FMyQueueOut:=AMyQueueOut;
  FPackedNumber:=0;
  inherited Create(False);
end;

procedure TMyThread.Execute;
var AMyMessage : TMyMessage;
    S : String;
begin
  while (not Terminated) do
  begin
    S:='';
    if FMyQueueIn.PopItem(AMyMessage) = TWaitResult.wrSignaled then
      S:='Из главного потока получено: ' + AMyMessage.StringMessage;
    Inc(FPackedNumber);
    with AMyMessage do
    begin
      StringMessage:=S;
      PackedNumber:=FPackedNumber;
      X:=Random(1000);
      Y:=Random(1000);
      Z:=Random(1000);
    end;
    // Отправляем Record в главную форму (главный поток)
    FMyQueueOut.PushItem(AMyMessage);
    sleep(random(800));
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FMyQueueIn:=TMyQueue.Create(30, 1000, 10);
  FMyQueueOut:=TMyQueue.Create(30, 1000, 10);;
  Application.OnIdle:=OnIdle;
  // При передаче в поток меняем местами In и Out - чтоб к контексте потока Out формы был In потока и наоборот.
  FMyThread:=TMyThread.Create(FMyQueueOut, FMyQueueIn);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FMyThread.Terminate;
  FMyThread.WaitFor;
  FMyThread.Free;
  FMyQueueIn.Free;
  FMyQueueOut.Free
end;

procedure TForm1.OnIdle(Sender: TObject; var Done: Boolean);
Var AMyMessage : TMyMessage;
    S : String;
begin
  while FMyQueueIn.PopItem(AMyMessage) = TWaitResult.wrSignaled do
  begin
    S:='Пакет№ ' + AMyMessage.PackedNumber.ToString +
      ' X=' + AMyMessage.X.ToString +
      ' Y=' + AMyMessage.Y.ToString +
      ' Z=' + AMyMessage.Z.ToString +
      ' ' + AMyMessage.StringMessage;
    Memo.BeginUpdate;
    Memo.Lines.Add(S);
    Memo.GoToTextEnd;
    Memo.EndUpdate;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
Var AMyMessage : TMyMessage;
begin
  AMyMessage.StringMessage:='Сообщение для потока № ' + Random(100).ToString;
  FMyQueueOut.PushItem(AMyMessage);
end;

end.

 

test161.zip

Изменено пользователем Евгений Корепов
Ссылка на комментарий
  • 0

Добавлю - вместо OnIdle, очередь можно проверять по таймеру. 

В TMyMessage = record не планировал вначале добавлять StringMessage, но без него в Rio косяк - компилятор выдает ошибку https://quality.embarcadero.com/browse/RSP-21806

image.thumb.png.282b8cd642a7bc0fe7a9aab34b6b4fd4.png

Изменено пользователем Евгений Корепов
Ссылка на комментарий
  • 0
11 часов назад, ddr 2 сказал:

нужно учитывать и контролировать, что бы поток не завершился раньше, чем все "сообщения" будут обработаны основным потоком? 

Это зависит от того, какого эффекта надо добиться.
Если queue вызываете из не-анонимного потока кодом вида Self.Queue(myProcedure) - то при уничтожении потока всё что он отдал в очередь на выполнение в главный поток, будет уничтожено. Без выполнения.
Если вызывать как TThread.Queue(nil, myProcedure) - то не будет уничтожено. Но в этом случае, само собой, нельзя обращаться к полям и методам этого потока. Если требуется обращение только к полям - заведите локальные переменные и работайте с ними. Например, так:

procedure TmyThread.Execute;
var
  SomeValue, SomeAnotherValue: string;
begin
  .....
  SomeValue:=Self.FInternalValue; // Self пишу только для понимания, что Fxxx - это поле в экземпляре потока
  SomeAnotherValue:=Self.FInternalAnotherValue;
  TThread.Queue(nil, procedure
    begin
      ShowMessage(SomeValue + SomeAnotherValue); // эти переменные доживут до выполнения анонимки даже если потока уже не будет.
    end;
  );

 

Ссылка на комментарий
  • 0
В 06.02.2019 в 00:02, Евгений Корепов сказал:

вместо OnIdle, очередь можно проверять по таймеру

Таймер был моей самой первой реализацией, но он негативно сказывается на отзывчивости интерфейса. В качестве стресс-теста на отзывчивость - в Windows перемещение окна мышкой. Таймер вызывает лаг(и) при перемещении окна. OnIdle, я раньше не пробовал. Попробовал- работает "мягче"- лаг не замечен. Кстати этот стресс-тест создаёт "пропуск" пакетов на экране, но это не критично и решается созданием более длинной очереди(1).

В 06.02.2019 в 08:26, kami сказал:
В 05.02.2019 в 21:03, ddr 2 сказал:

нужно учитывать и контролировать, что бы поток не завершился раньше, чем все "сообщения" будут обработаны основным потоком? 

Это зависит от того, какого эффекта надо добиться.

Извините, я не правильно сформулировал вопрос. Меня заботило, то, что при закрытии формы, в момент, когда мы в FormDestroy завершаем поток TMyThread, у нас в очереди могут
быть(остаться) сообщения/пакеты, которые никогда не будут обработаны, т.е. фактически это потерянные данные. Если в TMyThread.Execute, убрать sleep(random(800)), то данный
эффект наблюдается, как в моём предыдущем варианте с подпиской на сообщения, так и в варианте Евгения. И на решении этой локальной задачи, я признал, вариант Евгения лучшим
из имеющихся, потому, что в его варианте эта задача решается достаточно просто(ниже), а в варианте с подпиской, возможно решение и существует, но я пас.
Вариант решения задачи "потерянных данных"(2), заменяем 

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FMyThread.Terminate;
  FMyThread.WaitFor;
  FMyThread.Free;
  FMyQueueIn.Free;
  FMyQueueOut.Free
end;

на 

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var TempBoolean:Boolean;
begin
  FMyThread.Terminate;
  FMyThread.WaitFor;
  onIdle(Sender,TempBoolean);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FMyThread.Free;
  FMyQueueIn.Free;
  FMyQueueOut.Free
end;

Так же для оптимизации, в TForm1.OnIdle заменил условие выполнения цикла(3).

procedure TForm1.OnIdle(Sender: TObject; var Done: Boolean);
Var AMyMessage : TMyMessage;
    S : String;
begin
  while FMyQueueIn.QueueSize>0 do
  begin
    AMyMessage:=FMyQueueIn.PopItem;
    S:='Пакет№ ' + AMyMessage.PackedNumber.ToString +
      ' X=' + AMyMessage.X.ToString +
      ' Y=' + AMyMessage.Y.ToString +
      ' Z=' + AMyMessage.Z.ToString +
      ' ' + AMyMessage.StringMessage;
    Memo.BeginUpdate;
    Memo.Lines.Add(S);
    Memo.GoToTextEnd;
    Memo.EndUpdate;
  end;
end;

В новом варианте при каждом вызове OnIdle, если в очереди(TThreadedQueue) нет "сообщений"/элементов, то происходит лишь обращение к полю и сравнение, а ранее был вызов метода TThreadedQueue.PopItem в котором выполняется много всего и большая часть в критической секции.

Вместо резюме: Всем спасибо за ответы! Для себя в решении задачи двустороннего обмена сообщениями между формой и потоком, остановился на варианте Евгения с правками (1)(2)(3)
Углубившись в проблематику, пришёл к выводу, что в кроссплатформенном FMX лучше избегать архитектуры двусторонней связи формы с потоком, а пользоваться передачей данный из 
формы в запускаемый поток, через конструктор потока, а обратно, через обработчик onTerminate.

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

Вот вам еще пища для размышления и экспериментов. По моему мнению самый простой способ передачи из потока в главную форму:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  Generics.Collections,
  System.Generics.Collections, FMX.Controls.Presentation, FMX.ScrollBox,
  FMX.Memo, FMX.StdCtrls, System.Rtti, FMX.Grid.Style, FMX.Media, FMX.Grid;

type

  TMyMessage = record
    StringMessage : String; //Я вынужден был добавить еще строку, иначе в Rio косяк https://quality.embarcadero.com/browse/RSP-21806
    PackedNumber : Integer;
    X : Integer;
    Y : Integer;
    Z : Integer;
  end;

  TOnReceiveDataEvent = procedure(const AMyMessage : TMyMessage) of object;

  TMyQueue = TThreadedQueue<TMyMessage>;

  TMyThread = class(TThread)
  protected
    FMyQueueIn : TMyQueue;
    FOnReceiveData : TOnReceiveDataEvent;
    FPackedNumber : Integer;
    procedure Execute; override;
  public
    constructor Create(AMyQueueIn : TMyQueue);
    property OnReceiveData: TOnReceiveDataEvent read FOnReceiveData write FOnReceiveData;
  end;

  TForm1 = class(TForm)
    Memo: TMemo;
    ToolBar1: TToolBar;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    FMyQueueOut: TMyQueue;
    FMyThread : TMyThread;
    procedure OnReceiveData(const AMyMessage : TMyMessage);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

constructor TMyThread.Create(AMyQueueIn : TMyQueue);
begin
  FMyQueueIn:=AMyQueueIn;
  FPackedNumber:=0;
  inherited Create(True);
end;

procedure TMyThread.Execute;
var AMyMessage : TMyMessage;
    S : String;
begin
  while (not Terminated) do
  begin
    S:='';
    if FMyQueueIn.PopItem(AMyMessage) = TWaitResult.wrSignaled then
      S:='Из главного потока получено: ' + AMyMessage.StringMessage;
    Inc(FPackedNumber);
    with AMyMessage do
    begin
      StringMessage:=S;
      PackedNumber:=FPackedNumber;
      X:=Random(1000);
      Y:=Random(1000);
      Z:=Random(1000);
    end;
    // Отправляем Record в главную форму (главный поток)
    if Assigned(FOnReceiveData) then
      // Тут на выбор три метода доставки :-)
//      TThread.Synchronize(Self,
//      TThread.ForceQueue(Self,
      TThread.Queue(Self,
        procedure
        begin
          FOnReceiveData(AMyMessage);
        end
      );
    sleep(random(800));
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FMyQueueOut:=TMyQueue.Create(30, 1000, 10);
  FMyThread:=TMyThread.Create(FMyQueueOut);
  FMyThread.OnReceiveData:=OnReceiveData;
  FMyThread.Start;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FMyThread.Terminate;
  FMyThread.WaitFor;
  FMyThread.Free;
  FMyQueueOut.Free;
end;

procedure TForm1.OnReceiveData(const AMyMessage : TMyMessage);
Var S : String;
begin
  S:='Пакет№ ' + AMyMessage.PackedNumber.ToString +
    ' X=' + AMyMessage.X.ToString +
    ' Y=' + AMyMessage.Y.ToString +
    ' Z=' + AMyMessage.Z.ToString +
    ' ' + AMyMessage.StringMessage;
  Memo.BeginUpdate;
  Memo.Lines.Add(S);
  Memo.GoToTextEnd;
  Memo.EndUpdate;
end;

procedure TForm1.Button1Click(Sender: TObject);
Var AMyMessage : TMyMessage;
begin
  AMyMessage.StringMessage:='Сообщение для потока № ' + Random(100).ToString;
  FMyQueueOut.PushItem(AMyMessage);
end;

end.

 

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

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

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

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

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

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

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

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

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

  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...