Search code examples
multithreadingdelphidelphi-xe7delphi-xe8

How to check from outside whether ITask thread is still running?


In Delphi XE7 (or XE8), put a TjvProgressDialog (from JVCL) on a form and name it dlgProgress1. And a TButton and name it btnProgressDialogTest.

This code first starts Notepad from a separate thread (ShellExecAndWaitTask), then it opens the progress dialog (dlgProgress1) with an infinite progress loop:

var
  ShellExecAndWaitTask: System.Threading.ITask;

procedure TForm1.btnProgressDialogTestClick(Sender: TObject);
begin
  ShellExecAndWaitTask := TTask.Create(
    procedure
    begin
      JclShell.ShellExecAndWait('notepad'); // BTW, is this thread-safe?
      CodeSite.Send('Notepad has been closed');
    end);
  ShellExecAndWaitTask.Start;

  dlgProgress1.Caption := 'ProgressDialog Test';
  dlgProgress1.Text := 'Close Notepad to automatically close this progress dialog';
  dlgProgress1.Tag := 0;
  dlgProgress1.Position := 0;
  dlgProgress1.ShowCancel := True;
  dlgProgress1.ShowModal;
  CodeSite.Send('Progress dialog has been closed');
end;

procedure TForm1.dlgProgress1Progress(Sender: TObject; var AContinue: Boolean);
begin
  if dlgProgress1.Tag = 0 then
  begin
    if dlgProgress1.Position < dlgProgress1.Max then
      dlgProgress1.Position := dlgProgress1.Position + 1;
    if dlgProgress1.Position = dlgProgress1.Max then
      dlgProgress1.Tag := 1;
  end
  else
  begin
    if dlgProgress1.Position > 0 then
      dlgProgress1.Position := dlgProgress1.Position - 1;
    if dlgProgress1.Position = 0 then
      dlgProgress1.Tag := 0;
  end;

  AContinue := Assigned(ShellExecAndWaitTask); // why this never becomes false?
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(ShellExecAndWaitTask) then
    ShellExecAndWaitTask.Cancel;
end;

When you close Notepad, shouldn't Assigned(ShellExecAndWaitTask) in the dlgProgress1Progress event handler become false and close the progress dialog by setting AContinue to false? Instead it stays always true although the ShellExecAndWaitTask task has been terminated! Why?

EDIT:

Following David's advice I changed the code. Now it works, but is it thread-safe?

var
  ShellExecAndWaitTask: System.Threading.ITask;
  ShellExecAndWaitTaskTerminated: Boolean;

procedure TForm1.btnProgressDialogTestClick(Sender: TObject);
begin
  ShellExecAndWaitTask := TTask.Create(
    procedure
    begin
      JclShell.ShellExecAndWait('notepad'); // BTW, is this thread-safe?
      CodeSite.Send('Notepad has been closed');
      TThread.Queue(TThread.CurrentThread,
      procedure
      begin
        ShellExecAndWaitTaskTerminated := True;
      end);
    end);
  ShellExecAndWaitTaskTerminated := False;
  ShellExecAndWaitTask.Start;

  dlgProgress1.Caption := 'ProgressDialog Test';
  dlgProgress1.Text := 'Close Notepad to automatically close this progress dialog';
  dlgProgress1.Tag := 0;
  dlgProgress1.Position := 0;
  dlgProgress1.ShowCancel := True;
  dlgProgress1.ShowModal;
  CodeSite.Send('Progress dialog has been closed');
end;

procedure TForm1.dlgProgress1Progress(Sender: TObject; var AContinue: Boolean);
begin
  if dlgProgress1.Tag = 0 then
  begin
    if dlgProgress1.Position < dlgProgress1.Max then
      dlgProgress1.Position := dlgProgress1.Position + 1;
    if dlgProgress1.Position = dlgProgress1.Max then
      dlgProgress1.Tag := 1;
  end
  else
  begin
    if dlgProgress1.Position > 0 then
      dlgProgress1.Position := dlgProgress1.Position - 1;
    if dlgProgress1.Position = 0 then
      dlgProgress1.Tag := 0;
  end;
  AContinue := not ShellExecAndWaitTaskTerminated;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(ShellExecAndWaitTask) then
    ShellExecAndWaitTask.Cancel;
end;

Solution

  • When you close Notepad, shouldn't Assigned(ShellExecAndWaitTask) in the dlgProgress1Progress event handler become false and close the progress dialog by setting AContinue to false?

    No, that's never how things work. For instance you could have many different variables that reference the task. How could you expect all of those variables to be set to nil.

    No, the interface reference variable remains assigned until it leaves scope, or you set it to nil. As an aside, I seriously question your choice of using a global variable for this. I don't know why you made that decision, but it seems to be the wrong decision.

    The simplest thing for you to do is to send a message when the call to ShellExecAndWait returns.

    ShellExecAndWaitTask := TTask.Create(
      procedure
      begin
        JclShell.ShellExecAndWait('notepad');
        CodeSite.Send('Notepad has been closed');
        // notify the main thread that the external process has completed
      end);
    

    You could use, for instance, PostMessage or TThread.Queue or TThread.Synchronize to notify the main thread.