ddr 2 Опубликовано 2 февраля, 2019 Поделиться Опубликовано 2 февраля, 2019 Привет Под WIN32/64: - сообщение из формы в поток отправлял PostThreadMessage(myThread.ThreadID, WM_user+1, nativeuint(@data1),nativeuint(@data2)); - сообщение из потока в форму SendMessage(FormHandle,WM_user+2,nativeuint(@data1),nativeuint(@data2)); В Android такого нет. Подскажите, как организовать обмен сообщениями между формой и потоками в Android? Цитата Ссылка на комментарий
1 Евгений Корепов Опубликовано 2 февраля, 2019 Поделиться Опубликовано 2 февраля, 2019 Поищите на этом форуме или в гугле TThreadedQueue - универсальный метод под все платформы. Цитата Ссылка на комментарий
0 ddr 2 Опубликовано 4 февраля, 2019 Автор Поделиться Опубликовано 4 февраля, 2019 Не знал про 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 kami Опубликовано 4 февраля, 2019 Поделиться Опубликовано 4 февраля, 2019 5 часов назад, ddr 2 сказал: в чем ошибка? Поставьте бряк внутри метода _ReciveMessage и удивитесь - в каком потоке он вызывается. Ну или напишите if TThread.Current.ThreadID<>MainThreadID then // мог слегка ошибиться с наименованиями переменных - пишу по памяти, но вроде это они и есть. Алярма!!! (тут что-нибудь сигнализирующее что всё неправильно). Включая режим буквоедства: смените названия на ReceiveMessage и PackedNumber. Глаз цепляется Цитата Ссылка на комментарий
0 ddr 2 Опубликовано 5 февраля, 2019 Автор Поделиться Опубликовано 5 февраля, 2019 В 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 Евгений Корепов Опубликовано 5 февраля, 2019 Поделиться Опубликовано 5 февраля, 2019 (изменено) На основе вашего кода накидал свой вариант. Главная форма создает 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 Изменено 5 февраля, 2019 пользователем Евгений Корепов Цитата Ссылка на комментарий
0 Евгений Корепов Опубликовано 5 февраля, 2019 Поделиться Опубликовано 5 февраля, 2019 (изменено) Добавлю - вместо OnIdle, очередь можно проверять по таймеру. В TMyMessage = record не планировал вначале добавлять StringMessage, но без него в Rio косяк - компилятор выдает ошибку https://quality.embarcadero.com/browse/RSP-21806 Изменено 5 февраля, 2019 пользователем Евгений Корепов Цитата Ссылка на комментарий
0 kami Опубликовано 6 февраля, 2019 Поделиться Опубликовано 6 февраля, 2019 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 ddr 2 Опубликовано 8 февраля, 2019 Автор Поделиться Опубликовано 8 февраля, 2019 В 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 Евгений Корепов Опубликовано 8 февраля, 2019 Поделиться Опубликовано 8 февраля, 2019 Вот вам еще пища для размышления и экспериментов. По моему мнению самый простой способ передачи из потока в главную форму: 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. Цитата Ссылка на комментарий
Вопрос
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?
Ссылка на комментарий
9 ответов на этот вопрос
Рекомендуемые сообщения
Присоединяйтесь к обсуждению
Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.