Search code examples
multithreadingdelphicursorfiremonkey

How to enable an AniIndicator1 in a TThread , not in the main process in FMX?


Hi I have a multidevice APP based on FMX.

The synch database process takes longer time, I need a AniIndicator1 enabled to tell user to wait.

Following code tested in n android phone, sometime works, sometime only finish the first synch DB function, sometimes finish first 3 and then exited. Sometimes none of the sychDB function was taken place.

procedure TForm1.BtnSyncDBClick(Sender: TObject);
begin 
  Application.ProcessMessages;
  memo3.Lines.Add('Start synchronising...');
  AniIndicator1.Visible:=true;
  AniIndicator1.Enabled:=true;
      TThread.CreateAnonymousThread(
    procedure
    begin
      try
        SynchDB_A;
          memo3.Lines.Add('finish 0');
         SynchDB_B;
         memo3.Lines.Add('finish 1');
         SynchDB_C ;
          memo3.Lines.Add('finish 2');
         SynchDB_D;
          memo3.Lines.Add('finish 3');
      finally
        AniIndicator1.Enabled := False;
        AniIndicator1.Visible := False;
      end;
    end
    ).start;
end;

So, I am wondering if I can put the AniIndicator1.enable function in a child thread and use the main thread to synchDB?

Tried following code , not working as expected.

procedure TForm1.BtnSyncDBClick(Sender: TObject);
    begin 
           TThread.CreateAnonymousThread(
    procedure
    begin
      try
         TThread.Synchronize (TThread.CurrentThread,
          procedure ()
          begin
           Memo1.lines.Add('Start... ');
            AniIndicator1.Visible := True;
            AniIndicator1.Enabled := True;
          end);    
      finally

      end;
    end
  ).Start;

          try
            SynchDB_A;
              memo3.Lines.Add('finish 0');
             SynchDB_B;
             memo3.Lines.Add('finish 1');
             SynchDB_C ;
              memo3.Lines.Add('finish 2');
             SynchDB_D;
              memo3.Lines.Add('finish 3');
          finally
            AniIndicator1.Enabled := False;
            AniIndicator1.Visible := False;
          end;
end;

Any expert could help with this , either fix the first code to guarantee always work, child thread not being killed, or start SynchDB in the main process and enable AniIndicator1 in a child thread and make it spinning. Or any other easy way to tell user to wait while the synchDB working?

thanks in advance.


Solution

  • You can't directly access UI controls from within a worker thread, like you are doing. You MUST synchronize that access to the main UI thread.

    procedure TForm1.BtnSyncDBClick(Sender: TObject);
    var
      Thread: TThread;
    begin 
      Application.ProcessMessages;
      memo3.Lines.Add('Start synchronising...');
      AniIndicator1.Visible := True;
      AniIndicator1.Enabled := True;
      Thread := TThread.CreateAnonymousThread(
        procedure
        begin
          SynchDB_A; // <-- must do thread-safe work!
          TThread.Queue(nil,
            procedure
            begin
              memo3.Lines.Add('finish 0');
            end
          );
          SynchDB_B; // <-- must do thread-safe work!
          TThread.Queue(nil,
            procedure
            begin
              memo3.Lines.Add('finish 1');
            end
          );
          SynchDB_C; // <-- must do thread-safe work!
          TThread.Queue(nil,
            procedure
            begin
              memo3.Lines.Add('finish 2');
            end
          );
          SynchDB_D; // <-- must do thread-safe work!
          TThread.Queue(nil,
            procedure
            begin
              memo3.Lines.Add('finish 3');
            end
          );
        end
      );
      Thread.OnTerminate := SyncDone;
      Thread.Start;
    end;
    
    procedure TForm1.SyncDone(Sender: TObject);
    begin
      AniIndicator1.Enabled := False;
      AniIndicator1.Visible := False;
      if TThread(Sender).FatalException <> nil then
        memo3.Lines.Add('Error: ' + Exception(TThread(Sender).FatalException).Message);
    end;
    

    The reason your second code doesn't work is because the main UI thread is blocking doing all of the work, so it can't update the UI until that work is finished. Hence the need for doing non-UI work in a thread.