Question

I have reduced the code to a simple sample. IdTCPClient reads a message and should display it in a Memo. The attached code works fine on Windows, but the DoNotify is not executed on android if PostLog is called from ThC_Receive. If I call PostLog via Button.Click from MainForm it is executed. Any suggestions? TIdSync works, but is it recommended?

unit HeaderFooterTemplate;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls,   IdContext, IdSync,
  FMX.Layouts, FMX.Memo, IdThreadComponent, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdGlobal;

type
  // ---------------------------------------------------------------------------
  TLog = class(TIdNotify)
  protected
    fMsg: String;
    procedure DoNotify; override;
    //procedure DoSynchronize; override;
  public
    class procedure PostLog(const S: String);
  end;

  // ---------------------------------------------------------------------------
  THeaderFooterForm = class(TForm)
    Header: TToolBar;
    Footer: TToolBar;
    HeaderLabel: TLabel;
    M_Log: TMemo;
    IdTCPClient1: TIdTCPClient;
    ThC_Receive: TIdThreadComponent;
    Button2: TButton;
    procedure Button2Click(Sender: TObject);
    procedure IdTCPClient1Connected(Sender: TObject);
    procedure ThC_ReceiveRun(Sender: TIdThreadComponent);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  HeaderFooterForm: THeaderFooterForm;

implementation

{$R *.fmx}

// -----------------------------------------------------------------------------
{ TLog }
// -----------------------------------------------------------------------------
procedure TLog.DoNotify;
begin
  HeaderFooterForm.M_Log.Lines.Append(fMsg);
end;

// -----------------------------------------------------------------------------
class procedure TLog.PostLog(const S: String);
begin
  with Create do
  begin
    try
      fMsg := S;
      Notify;
    except
      Free;
      Raise;
    end;
  end;
end;

// -----------------------------------------------------------------------------
procedure THeaderFooterForm.Button2Click(Sender: TObject);
begin
  try
    IdTCPClient1.Host :='192.168.1.12';
    IdTCPClient1.Port := 1001;
    IdTCPClient1.Connect;
  except
      on E: Exception do
      begin
        TLog.PostLog(trim(e.Message));
        Raise;
      end;
  end;
end;

// -----------------------------------------------------------------------------
procedure THeaderFooterForm.IdTCPClient1Connected(Sender: TObject);
begin
  ThC_Receive.Start;
end;

// -----------------------------------------------------------------------------
procedure THeaderFooterForm.ThC_ReceiveRun(Sender: TIdThreadComponent);
var
  s: string;

begin
  try
    s:= (IdTCPClient1.IOHandler.ReadLn(TransmissionSeparator, IndyTextEncoding(IdTextEncodingType.encUTF16LE)));
      TLog.PostLog(trim(s));
  except
    on E: Exception do
    begin
      TLog.PostLog(trim(e.Message));
      Raise;
    end;
  end;
end;

// -----------------------------------------------------------------------------
end.

Thank you, Spectro

Was it helpful?

Solution

When called in a worker thread, TIdNotify uses TThread.Queue() and TIdSync uses TThread.Synchronize(), both of which go through the same RTL queue, so it is unlikely that TThread.Synchronize() would work but TThread.Queue() would not. On the other hand, when called in the main thread, TThread is not used (unless TIdNotify.MainThreadUsesNotify is set to true), TIdNotify.DoNotify() and TIdSync.DoSynchronize() are called directly instead. If TIdNotify is not working in a thread, then TThread.Queue() has to be broken, which would be an Embarcadero issue, not an Indy issue.

That being said, TIdNotify does have logic to use TThread.Synchronize() on Delphi versions that do not have TThread.Queue() available. You could try making a copy of IdSync.pas and modify it to undefine the HAS_STATIC_TThread_Queue define on Android, and then add the modified file to your project. That only works when Runtime Packages are disabled, though. I am not an Android developer, so I do not know how Delphi uses Packages on mobile platforms.

BTW, IndyTextEncoding(IdTextEncodingType.encUTF16LE) can be replaced with IndyTextEncoding_UTF16LE.

Update: a bug has been identified in FireMonkey itself (see QC #123579), going all the way back to when FireMonkey was first introduced, and still exists in XE5 Update 2. In short, FMX.TApplication does not assign a handler to the System.Classes.WakeMainThread callback (Vcl.TApplication does). TThread calls WakeMainThread to notify the main thread that a Synchronize/Queue() request is pending. Thus, if the main message loop is idle when Synchronize/Queue() is called, nothing happens until a later time if/when something else puts a new message in the main message queue. Without that, TApplication.Idle() is not called, and so CheckSynchronize() is not called to process pending Synchronize/Queue() requests. That means for background/nonvisual processes, Synchronize/Queue() may never work correctly at all, and sporadically in GUI processes. Until Embarcadero fixes that bug, a workaround would be to either post a custom message to the main message queue to "wake" it up, or else manually call CheckSynchronize() in the main thread periodically, such as in a timer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top