Why is Application.ProcessMessages necessary for TRzLauncher.Running?

Home Forums Konopka Signature VCL Controls (formerly Raize Components) Why is Application.ProcessMessages necessary for TRzLauncher.Running?

Viewing 5 reply threads
  • Author
    Posts
    • #3423
      David Marcus
      Participant

        I don’t whether this is a bug, but regardless I would like to understand why this works the way that it does. I have RzLauncher1.WaitUntilFinished = False. In my app, I do

        while RzLauncher1.Running do
        sleep( 2000 );

        But, this loop never ends. I need to do

        while RzLauncher1.Running do begin
        sleep( 2000 );
        Application.ProcessMessages;
        end;

        Why is this? That is, why do I need to call Application.ProcessMessages?

        I’m using Delphi 11.2.

        To see this in action, create two apps. LaunchTest is a console app:

        {$apptype console}
        program LaunchTest;
        uses
        System.SysUtils;
        begin
        System.SysUtils.Sleep( 10000 );
        end.

        Project1 is a VCL app with one unit:

        unit Unit1;
        interface
        uses
        Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
        Vcl.Controls, Vcl.Forms, Vcl.Dialogs, RzLaunch, Vcl.StdCtrls;
        type
        TForm1 = class(TForm)
        RzLauncher1: TRzLauncher;
        Label1: TLabel;
        Button1: TButton;
        Label2: TLabel;
        procedure Button1Click(Sender: TObject);
        end;
        var
        Form1: TForm1;
        implementation
        {$R *.dfm}
        procedure TForm1.Button1Click(Sender: TObject);
        var
        J: integer;
        begin
        Label2.Caption := ”;
        RzLauncher1.Launch;
        for J := 0 to 20 do begin
        sleep( 1000 );
        Label1.Caption := IntToStr( J ) + ‘ ‘ + BoolToStr( RzLauncher1.Running, true );
        //Application.ProcessMessages;
        end;
        Application.ProcessMessages;
        Label2.Caption := BoolToStr( RzLauncher1.Running, true );
        end;
        end.

        object Form1: TForm1
        Left = 0
        Top = 0
        Caption = ‘Form1’
        ClientHeight = 433
        ClientWidth = 622
        Color = clBtnFace
        Font.Charset = DEFAULT_CHARSET
        Font.Color = clWindowText
        Font.Height = -12
        Font.Name = ‘Segoe UI’
        Font.Style = []
        TextHeight = 15
        object Label1: TLabel
        Left = 48
        Top = 48
        Width = 34
        Height = 15
        Caption = ‘Label1’
        end
        object Label2: TLabel
        Left = 48
        Top = 80
        Width = 34
        Height = 15
        Caption = ‘Label2’
        end
        object Button1: TButton
        Left = 56
        Top = 112
        Width = 75
        Height = 25
        Caption = ‘Button1’
        TabOrder = 0
        OnClick = Button1Click
        end
        object RzLauncher1: TRzLauncher
        Action = ‘Open’
        FileName = ‘C:\Mine\Test\LaunchTest.exe’
        Timeout = -1
        Left = 240
        Top = 32
        end
        end

        Change RzLauncher1.FileName to point to the LaunchTest.exe. Run Project1. Click the button. After 20 seconds, the labels will display

        20 True
        False

        Uncomment the Application.ProcessMessages statement in the loop. Run Project1, and click the button. After 10 seconds, the first label displays

        11 False

      • #3428
        Ray Konopka
        Keymaster

          Hi David,

          I’m a little confused by what you are trying to accomplish given your example. When using the Launch method to launch another app, you have the choice to wait for the launched app to finished (WaitUntilFinished := True), which means that the call to Launch blocks and only returns when the launched app terminates. If WaitUntilFinished is set to False, the Launch method returns immediately and your app continues to run, *and* when the launched app terminates the OnFinished event is fired.
          In your sample code, you are launching the secondary app and then trying to use a loop to essentially wait for the launched app to terminate. I would suggest writing an OnFinished event handler instead. If you need to update your UI while the launched app is running, for example to prevent the user from doing certain activities, you can set that up before calling Launch. Then when OnFinished is invoked, you can update the various flags so that the user will be able to continue.

          Ray

        • #3432
          David Marcus
          Participant

            Ray,

            I just tried the OnFinished event. I had it set a boolean field of the form
            when it fired. It has the same behavior: I need to call
            Application.ProcessMessages for the boolean field to be set.

            My app launches the secondary app, then continues to do other stuff. But,
            there are some things I don’t want the app to do while the launched process
            is still running.

            The way you outlined it, the main app would be idle while the launched
            process finishes, so the VCL would call ProcessMessages. If my app already
            knows what it wants to do next, but doesn’t want to start doing it until
            the secondary app finishes, it needs to wait. That’s why I have the loop.
            Maybe I can rearrange things so my app doesn’t need to wait.

            Regardless, I’d still like to understand why threads work this way. Is the
            following correct? The main app is thread 1. The TRzLaunchThread object is
            thread 2. The launched process is thread 3. The TRzLauncher object is
            passed to TRzLaunchThread, so both threads 1 and 2 can read/write the
            FRunning field. But, something doesn’t happen unless ProcessMessages is
            called; I don’t know what.

            David

          • #3435
            Ray Konopka
            Keymaster

              Hi David,

              I created test a project that I think may help explain things a bit. I dropped two TRzButton controls (btnLaunch and btnContinue) and a TRzLauncher onto the form. I also created a new enum called TAppState and created a corresponding private field, FAppState, to keep track of the current state of the app. The TRzLauncher has its FileName property set to ‘C:\Windows\Notepad.exe’. When the app starts, FAppState defaults to appInit. The btnLaunchClick event handler looks at the FAppState field and only launches Notepad if FAppState is not equal to appWaiting. Before calling RzLauncher1.Launch, FAppState is set to appWaiting.

              The btnContinueClick event handler also looks at the FAppState field, but only calls ShowMessage if the state is appContinue. The FAppState field is set to appContinue in the OnFinished event handler.

              When you start the app, clicking the Continue button does nothing since FAppState is appInit. Clicking the Launch button launches Notepad. While Notepad is running, clicking Launch or Continue does nothing. Once Notepad is closed, then clicking Continue will show the message box.

              The point is that it is not necessary to do any looping to accomplish what you want. In fact, it is the looping (in the UI thread) that is forcing you to call Application.ProcessMessages.

              Ray

              unit Unit26;
              
              interface
              
              uses
                Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
                Vcl.Controls, Vcl.Forms, Vcl.Dialogs, RzButton, RzLaunch, Vcl.StdCtrls, RzLabel;
              
              type
                TAppState = ( appInit, appWaiting, appContinue );
              
                TForm26 = class(TForm)
                  btnLaunch: TRzButton;
                  RzLauncher1: TRzLauncher;
                  btnContinue: TRzButton;
                  procedure btnLaunchClick(Sender: TObject);
                  procedure btnContinueClick(Sender: TObject);
                  procedure RzLauncher1Finished(Sender: TObject);
                private
                  { Private declarations }
                  FAppState: TAppState;
                public
                  { Public declarations }
                end;
              
              var
                Form26: TForm26;
              
              implementation
              
              {$R *.dfm}
              
              procedure TForm26.btnLaunchClick(Sender: TObject);
              var
                J: integer;
              begin
                if FAppState <> appWaiting then
                begin
                  FAppState := appWaiting;
                  RzLauncher1.Launch;
                end;
              end;
              
              procedure TForm26.btnContinueClick(Sender: TObject);
              begin
                if FAppState = appContinue then
                begin
                  ShowMessage( 'Allowed to Continue' );
                end;
              end;
              
              procedure TForm26.RzLauncher1Finished(Sender: TObject);
              begin
                FAppState := appContinue;
              end;
              
              end.
            • #3436
              David Marcus
              Participant

                Ray,

                Thank you. Your test project is what I was thinking of when I wrote “The
                way you outlined it, the main app would be idle while the launched process
                finishes, so the VCL would call ProcessMessages.”

                Currently, my app does more in the main thread than it should. So, I’d need
                to do a good bit of recoding to make it work like your test app. I have
                this on my to-do list.

                David

              • #3437
                David Marcus
                Participant

                  P.S. I’m still wondering why, in my test app, thread 1 needs to call ProcessMessages for thread 2 to update FRunning.

              Viewing 5 reply threads
              • You must be logged in to reply to this topic.