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

Form.Show в потоке. (called from wrong thread exception)


notricky

Вопрос

На Android код, который работает исправно на Win  вызывает ошибку "CalledFromWrongThreadException: Only the original thread that created a view hierarcy can touch its views" 

Смысл таков, что я пытаюсь показать форму из треда, у которой BorderStyle=none (роли это не играет).

Решение в Андроиде заключается в том, чтобы пускать через  runOnUiThread  (то есть выполнять интерфейсные штуки в главном потоке). Как я понимаю, в firemonkey эту фичу должен выполнять Synchronize().
Тем не менее, ошибка возникает.
А при запуске в режиме дебага на андроид девайсе событие кнопки вообще не срабатывает иногда. А если срабатывает, то возникает описанное выше исключение.
Я собрал тестовый пример и в нем не сразу видна ошибка, тогда как получил я ее на рабочем проекте.
Цель: показать бизибокс на время бекграундных действий. Этот бизибокс у меня сначала был просто на каждой форме и я интерфейсно его вызывал, но теперь решил сделать отедльной формой (как и тоаст), но почему так происходит я не понял. Вы что скажете?

 

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TWorkThread = class(TThread)
  public
    procedure Execute; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses unit2;

procedure TForm1.Button1Click(Sender: TObject);
var t: TWorkThread;
begin
  t := TWorkThread.Create(True);
  t.FreeOnTerminate := true;
  t.Start;
end;

{ TWorkThread }

procedure TWorkThread.Execute;
begin
  inherited;
  Self.Synchronize(procedure 
  begin
    Form2.Show; 
	Form2.Top := Form1.Top; 
	Form2.Left := Form1.Left;
    Form2.BringToFront;
  end);
  Terminate;
end;

end.

 

Изменено пользователем notricky
Ссылка на комментарий

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

  • 0

Почему так - скажут знатоки андроида. Емнип, UIThread != MainThread (пока еще)

А вообще - посоветую взять из fgx ActivityDialog

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

Почему так - скажут знатоки андроида. Емнип, UIThread != MainThread (пока еще)

А вообще - посоветую взять из fgx ActivityDialog

Да, в комментах на Stackoverflow выяснили это с Remy Lebeau.

Решение - IFDEF-ить для андроида моменты синхронизации интерфейса через FMX.Platform.Android.TPlatformAndroid
Пока не проверял, но looking forward to :)
Если кто попробует и отпишется, будет классно всем на будущее.

fgx ActivityDialog
Не сочтите за наглость, O-kami :),  но если вкратце, то это про что?

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

поднимаемся до начала страницы

находим закладку FGX

Посмотрел. 

Этокак я понял компонент и его надо класть на каждую форму. У меня другая задача - форму-бизибокс сделать. Но за хинт спасибо. 

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

Этокак я понял компонент и его надо класть на каждую форму.

вот именно, что это - компонент, а не контрол. И его достаточно иметь в любом "общем" модуле, возможно - даже создать в runtime. В общем, его достаточно иметь в одном экземпляре. Но - дело ваше.

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

вот именно, что это - компонент, а не контрол. И его достаточно иметь в любом "общем" модуле, возможно - даже создать в runtime. В общем, его достаточно иметь в одном экземпляре. Но - дело ваше.

А у него таких проблем с отрисовкой/показом нет/решены?

А стилизацию/настройку цветов, прозрачности имеет?

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

Если кто зайдёт настолько далеко, то решение кроется в FMX.Helpers.AndroidCallInUIThreadAndWaitFinishing

{$IFDEF}  в нужных местах делает своё дело. Не смотрел внутрь компонентов FGX, может быть там сделано кросплатформеннее?

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

Нет, похоже и этот метод какой-то багонутый.

Последовательность внутри потока

    {$IFDEF ANDROID}
    CallInUIThreadAndWaitFinishing(ShowOverlay);
    Sleep(1000);
    CallInUIThreadAndWaitFinishing(ShowForm);
    Sleep(1000);
    CallInUIThreadAndWaitFinishing(HideOverlay);
    {$ELSE}

не всегда заканчивается тем, что HideOverlay реально хайдит оверлейную форму. То есть да, в метод хайда заходит (анимация останавливается), но непосредственно сам хайд окна заканчивается одним из трех вариантов, при том рандомно:

  • Анимация заморожена, окно висит
  • Окно скрыто, видны какие-то артефакты
  • Окно скрыто штатно

Ни в варианте прямого вызова Overlay.Hide

Ни в варианте TMessageSender.SendMessage().

Интересно вообще в чем именно заключается баг. Баг ли это или это нормальная работа Андроида...

Ссылка на комментарий
  • 0
  • Администраторы
8 часов назад, notricky сказал:

Буду признателен, если кто-то что-то по этому поводу скажет.

Все просто. нельзя работать с формой из другого потока. CallInUIThread именно и вызывает код из другого потока. Посему и нет никакой гарантии в работоспособности всего этого.

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

Все просто. нельзя работать с формой из другого потока. CallInUIThread именно и вызывает код из другого потока. Посему и нет никакой гарантии в работоспособности всего этого.

Я честно не понял. Ведь даже в нативном коде такой вызов является правильным решением. Разве CallInUIThread не выполняет код внтури основного (всмысле интерфейсного) потока?

И второй вопрос - пишут, что до ХЕ7 нужно было выполнять в таймере  CheckSync чтобы прокачивать очередь. Начиная с ХЕ7 пишут что пофиксен баг. Получается что SendMessage из рабочего потока в окна, созданные главным потоком должны правильно обрабатываться. Или тоже есть какие-то интересные особенности?

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

Ну то есть когда я в рабочем потоке делаю так:

var
  Form1: TForm1;
  MsgSender: TMessageSender;

procedure TWorkThread.ShowOverlay;
var V: Tvalue;
begin
  V := Tvalue.From(Form1.RectSelf);
  MsgSender.Receiver := TFOverlay.This;
  MsgSender.SendMessageWithResult(PM_OVERLAY_SHOW, V);
end;

...
procedure TWorkThread.Execute;
begin
  try
    ShowOverlay;
    Sleep(1000);
    HideOverlay;
  except
    on e: exception do MessageDlg(e.Message,TMsgDlgType.mtWarning, [TMsgDlgBtn.mbYes], 0, nil);
  end;
  Terminate;
end;


// OVERLAY
....
type
  TFOverlay = class(TForm)
    procedure PMShow(var AMessage: TDispatchMessageWithValue<TRect>); message PM_OVERLAY_SHOW;
  ...
  end;
      
implementation

procedure TFOverlay.PMShow(var AMessage: TDispatchMessageWithValue<TRect>);
begin
  FRect := AMessage.Value;
  This.Show;
end;

тем не менее я получаю ошибку  CalledFromWrongThreadException: Only the original thread that created a view hierarcy can touch its views" 

На самом деле если внутри рабочего потока ShowOverlay обернуть в CallInUIThread то ошибки нет, но, как описал Ярослав, негарантированно.

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

 

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

FMX, также как и VCL так же как и вообще все объекты (за исключением некоторых специальных) не потокобезопасны. Т.е. напрямую использовать в потоке что-либо созданное вне этого потока нельзя, потому, что нельзя никогда... Всякие там формы кнопки и пр. взаимодействуют с другими формами кнопками и пр. контролами, так что даже если вы создаете и уничтожаете форму внутри потока (внутри метода Execute), то всё равно не явно Вы обращаетесь к другим объектам системы. Чтобы решить эту проблему все обращения к контролам надо делать в методе  Synchronize По этому ключевому слову ищите информацию в интернете и изучайте многопоточность.

Но лучше просто не работайте с интерфейсом в потоке. В этом примере можно просто устанавливать и сбрасывать некий флаг FOverlayShown. К нему Вы можете обращаться из основного потока в любой момент (к FErrorMessage только после окончания работы потока). Обрабатывать OverlayShown можно например событии TAction.OnUpdate или хотя бы в TTimer.

procedure TWorkThread.Execute;
begin
  try
    FOverlayShown := True;
    try
      Sleep(1000);
    finally
      FOverlayShown := False;
    end;
  except
    on e: exception do
    begin
      FErrorMessage := e.Message;
    end;
  end;
end;

P.S. А лучше вообще не связывайтесь с потоками.

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

FMX, также как и VCL так же как и вообще все объекты (за исключением некоторых специальных) не потокобезопасны. Т.е. напрямую использовать в потоке что-либо созданное вне этого потока нельзя, потому, что нельзя никогда... Всякие там формы кнопки и пр. взаимодействуют с другими формами кнопками и пр. контролами, так что даже если вы создаете и уничтожаете форму внутри потока (внутри метода Execute), то всё равно не явно Вы обращаетесь к другим объектам системы. Чтобы решить эту проблему все обращения к контролам надо делать в методе  Synchronize По этому ключевому слову ищите информацию в интернете и изучайте многопоточность.

Но лучше просто не работайте с интерфейсом в потоке. В этом примере можно просто устанавливать и сбрасывать некий флаг FOverlayShown. К нему Вы можете обращаться из основного потока в любой момент (к FErrorMessage только после окончания работы потока). Обрабатывать OverlayShown можно например событии TAction.OnUpdate или хотя бы в TTimer.


procedure TWorkThread.Execute;
begin
  try
    FOverlayShown := True;
    try
      Sleep(1000);
    finally
      FOverlayShown := False;
    end;
  except
    on e: exception do
    begin
      FErrorMessage := e.Message;
    end;
  end;
end;

P.S. А лучше вообще не связывайтесь с потоками.

Зачем читать тему целиком, когда можно просто в белом выйти.

Ну ладно, не прочитали. Бывает.
Ответите тогда на вопрос - почему возникает поднятая проблема (да, для этого надо тему почитать) в случае с посылом сообщений классу формы из потока?

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

SendMessageWithResult

SendMessage через механизмы FMX !=SendMessage через механизмы Windows. Да, в Windows получатель обработает сообщение в том потоке, в котором принявшее сообщение окно было создано. Боюсь, что SendMessage из FMX не учитывает это, так что прием сообщения осуществляется в контексте вызывающего потока. Со всеми вытекающими.

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

SendMessage через механизмы FMX !=SendMessage через механизмы Windows. Да, в Windows получатель обработает сообщение в том потоке, в котором принявшее сообщение окно было создано. Боюсь, что SendMessage из FMX не учитывает это, так что прием сообщения осуществляется в контексте вызывающего потока. Со всеми вытекающими.

Глупость какая-то. Всмысле реализация - теряется весь смысл сообщений, в особенности если это сообщения на отрисовку. А где-то про это вообще написано?

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

А где-то про это вообще написано?

Я искал (перед тем, как написать предыдущее сообщение) - не нашел.

Посему - мой предыдущий пост написан именно на основе собственных умозаключений. В частности:

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

В FMX сообщения отправляются объекту. Объект - существо интернациональное, оно существует без привязки к какому-нибудь потоку, а некоторые - так вообще не могут существовать в пределах одного (TThread).

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

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

Я искал (перед тем, как написать предыдущее сообщение) - не нашел.

Я наткнулся на такую тему:
Оконные сообщения в FireMonkey

Правда про потоки там ни слова... Зато в коментах Ярослав Бровин присутствует :)

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

А знаете что я вам скажу?? А вот угадайте, в чем была на самом деле проблема???

А проблема была как всегда у Эмбаркадеро неявная, а именно в том, что BorderStyle = bsNone не дружит с вызовами вооообще. Ниоткуда. И вызывает вышеобозначенную ошибку в любом из возможных вариантов. И треды тут вообще не при чем. Ну то есть вот совсем не при чем. То есть если правильно задать BorderStyle, то вполне можно обойтись на Android оборачиванием вызовов Show/Hide TThread.Queue(...)

Часы.... просто часы тестов убиты на такой маразм.

Друзья, коллеги, если у вас есть последняя версия FMX, пожалуйста, протестируйте этот вариант с Бордером на андроиде. И отпишитесь здесь. И если это баг, то может быть стоит репортнуть. Для теста нужно две формы, одна из которых обычная, вторая borderStyle=None и вызывается кнопкой из первой формы. Обе создаются обычным способом при инициализации приложения.

Upd2:

Еще одно замечание.
Если выставлено bsNone, но при этом FullScreen = True, то другие формы открываются обычным Form.Show ровно до того момента, как было обращение на показ формы с bsNone и FullScreen = True. После этого последняя показывается, а вот все прочие формы, открывающиеся по кнопке обычным образом уже идут с ошибками  CalledFromWrongThreadException: Only the o....

Изменено пользователем notricky
Ссылка на комментарий
  • 0
В 26.01.2017 в 21:27, notricky сказал:

Зачем читать тему целиком, когда можно просто в белом выйти.

Да, действительно всю тему не прочитал, виноват. Вы тогда про 

    FOverlayShown := True;
    try
      Sleep(1000);
    finally
      FOverlayShown := False;
    end;

тоже не читайте, ибо малодушный поиск лёгких путей — это не спортивно ;)

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

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

Да, действительно всю тему не прочитал, виноват. Вы тогда про 


    FOverlayShown := True;
    try
      Sleep(1000);
    finally
      FOverlayShown := False;
    end;

тоже не читайте, ибо малодушный поиск лёгких путей — это не спортивно ;)

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

Все оказалось куда хуже. И сообщения работают, и синхронайз, только вот проблема была в BorderStyle. Почитайте два сообщения назад. И вот это возможно репорт для QC, если у кого-то еще такая же ситуация на новых дельфях возникает. Но пока никто не отписался.

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

Еще один странный баг, если кому интересно и кто знает что то про это.

 

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

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

Пока что решение такое: перед хайдом формы найти и активировать следующую активную форму (ту, вероятно, что под оверлеем)

 Screen.NextActiveForm(OverlayForm).Activate;

Внутри синхрониза естественно.

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

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

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

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

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

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

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

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

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

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