3

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?

Trent W
  • 95
  • 2
  • 9
  • Are you sure that claiming _"Need to wait until a file exists in a directory. The only solution that works is using Sleep/Application.ProcessMessages"_ is correct? I guess that `ReadDirectoryChangesW` might work here. – Victoria Jun 09 '17 at 22:07
  • 1
    See [Delphi notification when a file gets updated](https://stackoverflow.com/q/3418562/576719) or [File / Folder monitoring](https://stackoverflow.com/q/479770/576719). – LU RD Jun 09 '17 at 22:08
  • The linked answers will send a notification when there is news. Just have a timer in your program to display the seconds waiting. – LU RD Jun 09 '17 at 22:19
  • 1
    `waitThread.EventDone.WaitFor(INFINITE);` - this is death when you write it in a UI thread handler. How would expect this *not* to freeze your program? Don't wait for your thread - let *it* notify the main thread when it is done. `TThread.Queue` is one approach, or just use the `OnTerminate` event. – J... Jun 10 '17 at 01:24
  • 1
    You're using the thread entirely wrong. You've got your main thread waiting for the thread, which is exactly what you *do not* want to do. If your application has to wait until the file exists, then a thread isn't going to help; your app still has to wait. – Ken White Jun 10 '17 at 01:40
  • Don't call ProcessMessages in the thread – David Heffernan Jun 10 '17 at 07:25
  • There's more, don't wait for the event in the main thread, don't sleep in the thread, handle exceptions in the thread context, use directory notification.. – Victoria Jun 10 '17 at 08:10

2 Answers2

7

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I will look into using the `ReadDirectoryChangesW` function. It seems that the DirectoryWatch project is a good example for everything I'm trying to do so I will use it as a guide. – Trent W Jun 12 '17 at 13:44
2

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.

I don't think so. Proper way to achieve your task should be using ReadDirectoryChangesW function waiting for the FILE_ACTION_ADDED action to be signalled.

An example implementation you can find e.g. in Directory Watch project.

Victoria
  • 7,822
  • 2
  • 21
  • 44