Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi Threads - Freezing while waiting

I am writing an application that needs to wait until a file exists in a directory. I have tried multiple approaches to this, and the only solution that works is using Sleep/Application.ProcessMessages.

Here's what I have tried:

Using Sleep/Application.ProcessMessages:

Result := False;
for i := 0 to iTimeout do
begin
  if FileExists(fileName) do
  begin
    updateStatus('Conversion Completed');
    Result := True;
    Break;
  end;
  updateStatus(Format('Checking for file: %d Seconds', [i]));
  Application.ProcessMessages;
  Sleep(1000);
end;

This method works, except that I can't close the application while it's waiting. Also there are well documented issues with using Sleep/Application.ProcessMessages that I would rather avoid.

Using TThread/TEvent:

type
  TMyThread = class(TThread)
  private
    FEventDone: TEvent;
  public
    constructor Create(CreateSuspended: boolean);
    destructor Destroy;
    procedure Execute; override;
    property EventDone: TEvent read FEventDone;
  end;

  TformThreading = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    waitThread: TMyThread;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  formThreading: TformThreading;

implementation

{$R *.dfm}

{ TformThreading }

procedure TformThreading.Button1Click(Sender: TObject);
var
  res: TWaitResult;
begin
  try
    waitThread.Start;
    res := waitThread.EventDone.WaitFor(INFINITE);
    case res of
      wrSignaled: ShowMessage('wrSignaled');
      wrTimeout: ShowMessage('wrTimeout');
      wrAbandoned: ShowMessage('wrAbandoned');
      wrError: ShowMessage('wrError');
      wrIOCompletion: ShowMessage('wrIOCompletion');
    end;
  except
    on E: Exception do
    begin
      ShowMessage(E.Message);
    end;
  end;
end;

procedure TformThreading.FormCreate(Sender: TObject);
begin
  waitThread := TMyThread.Create(true);
end;

procedure TformThreading.FormDestroy(Sender: TObject);
begin
  waitThread.Free;
end;

{ TMyThread }

constructor TMyThread.Create(CreateSuspended: boolean);
begin
  inherited Create(CreateSuspended);
  FEventDone := TEvent.Create;
end;

destructor TMyThread.Destroy;
begin
  FEventDone.Free;
end;

procedure TMyThread.Execute;
begin
  for i := 0 to iTimeout do
  begin
    if FileExists(fileName) do
    begin
      FEventDone.SetEvent;
      Break;
    end;
    Application.ProcessMessages;
    Sleep(1000);
  end;
end;

I can't seem to get this to not freeze my main thread while it is waiting, but this seems like the correct approach if I can work out the freezing issue.

What is my best approach to solve my problem?

like image 345
Trent W Avatar asked Dec 09 '25 12:12

Trent W


1 Answers

I can't seem to get this to not freeze my main thread while it is waiting.

When you wait on an object, that thread blocks until the object is signaled. So when your main threas waits for the event it is blocked. The behaviour that you observe is exactly to be expected.

Looking at your thread's code, it loops until a condition is met, signals an event, and then terminates. In other words, the event serves no purpose. You could remove it and instead wait for the thread.

Now, if you did that your code would do this:

  1. Create a thread and start it executing.
  2. Wait for that thread to complete, blocking the main thread.
  3. Continue execution once the thread completed.

So, as it stands, your thread achieves nothing. You may as well execute its code in the main thread. You are back where you started.

You need a different mindset. GUI programs are asynchronous in their UI. You need to follow that pattern. Don't wait in the main thread. Instead, use a separate thread by all means, but have that thread signal to the main thread when it is done.

The very simplest way for you to do that here is to implement an OnTerminate event handler for the thread. That will fire when the thread completes its work. The event handler will execute in the main thread.

More generally you could use Synchronize or Queue to signal events to the main thread, but in your case handling the termination event meets your needs.

Now to the body of the thread. Your approach is based on polling, with a sleep. That will work but it's not terribly pretty. The ReadDirectoryChangesW function is the system provided mechanism for you to receive notifications of changes in the file system. That would be a more elegant approach.

My advice is to fix the blocking problems first using your current polling approach. Once that is clear re-factor to use ReadDirectoryChangesW.

like image 196
David Heffernan Avatar answered Dec 12 '25 08:12

David Heffernan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!