(06-22-2023, 03:51 PM)Neerarawat Wrote: The issue you're experiencing ...
First off, that felt like a ChatGPT-generated answer to me, but I'm going to address it anyway, because it provided a lot of misinformation that will get you into trouble.
(06-22-2023, 03:51 PM)Neerarawat Wrote: Here's an example of how you can achieve this using TThread
DO NOT call TIdFTP.Connected() from a different thread than the one that is performing the FTP operations. Connected() determines the socket state by reading from the socket, so you would be reading socket data from 2 threads at the same time, and thus risking the IOHandler.InputBuffer becoming corrupted, which would screw up the FTP operations.
A safer approach would be to wait for the thread itself to terminate after the FTP work is finished. Note that TThread.CreateAnonymousThread() creates a TThread object whose FreeOnTerminate property is set to True, which means you would not be able to use the TThread.WaitFor() method by default without crashing your code. But, you can just turn off FreeOnTerminate before starting the anonymous thread.
Also, since the FTP operations are being executed in a worker thread, the FTP.OnWork... event handlers will be called in the context of that thread, not in the context of the main UI thread. So, you would need to synchronize with the main thread when accessing UI controls.
Try something more like this instead:
Code:
procedure TForm1.DownloadFile(const AHost, AUsername, APassword, ARemoteFile, ALocalFile: string);
var
LThread: TThread;
procedure WaitForThread;
var
H: THandle;
begin
// LThread.WaitFor;
H := LThread.Handle;
repeat
case MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_ALLINPUT) of
WAIT_OBJECT_0+0: Break;
WAIT_OBJECT_0+1: Application.ProcessMessages;
else
RaiseLastOSError;
end;
until False;
end;
begin
// Connect and download the file in a separate thread
LThread := TThread.CreateAnonymousThread(
procedure
var
FTP: TIdFTP;
begin
FTP := TIdFTP.Create(nil);
try
FTP.Host := AHost;
FTP.Username := AUsername;
FTP.Password := APassword;
// Set up event handlers to track progress
FTP.OnWork := Self.FTPWork;
FTP.OnWorkBegin := Self.FTPWorkBegin;
FTP.OnWorkEnd := Self.FTPWorkEnd;
FTP.Connect;
try
FTP.Get(ARemoteFile, ALocalFile);
finally
FTP.Disconnect;
end;
finally
FTP.Free;
end;
end
);
try
LThread.FreeOnTerminate := False;
LThread.Start;
// Wait for the download to complete
WaitForThread;
finally
LThread.Free;
end;
// You can perform any post-download actions here
end;
procedure TForm1.FTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
// Update the progress bar based on the current work count
TThread.Queue(nil,
procedure
begin
ProgressBar1.Position := AWorkCount;
end
);
end;
procedure TForm1.FTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
// Show the progress bar and set its maximum value
TThread.Queue(nil,
procedure
begin
ProgressBar1.Visible := True;
ProgressBar1.Position := 0;
ProgressBar1.Max := AWorkCountMax;
end
);
end;
procedure TForm1.FTPWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
// Hide the progress bar when the FTP work ends
TThread.Queue(nil,
procedure
ProgressBar1.Visible := False;
end
);
end;
Which, to be honest, is really no better than simply eliminating the anonymous thread altogether and just having DownloadFile() use TIdFTP directly in the context of the calling thread, and using TIdAntiFreeze to avoid blockage if DownloadFile() is being called in the context of the main UI thread:
Code:
procedure TForm1.DownloadFile(const AHost, AUsername, APassword, ARemoteFile, ALocalFile: string);
var
FTP: TIdFTP;
begin
FTP := TIdFTP.Create(nil);
try
FTP.Host := AHost;
FTP.Username := AUsername;
FTP.Password := APassword;
// Set up event handlers to track progress
FTP.OnWork := FTPWork;
FTP.OnWorkBegin := FTPWorkBegin;
FTP.OnWorkEnd := FTPWorkEnd;
FTP.Connect;
try
FTP.Get(ARemoteFile, ALocalFile);
finally
FTP.Disconnect;
end;
finally
FTP.Free;
end;
// You can perform any post-download actions here
end;
That being said, regardless of whichever thread ends up using TIdFTP, do note that for TIdFTP.Get() (but not for TIdFTP.Put()), the AWorkCountMax parameter of the TIdFTP.OnWorkBegin event will always be 0, because the FTP protocol does not advertise data sizes during transfers. So, you would need to use either TIdFTP.Size(), TIdFTP.List(), or TIdFTP.ExtListItem() to determine a file's size before you then use TIdFTP.Get() to download the file. I'll leave that as an exercise for you to add that to the above example.