Быстрое и грязное решение:
type
TFileCopyThread = class(TThread)
private
fCS: TCriticalSection;
fDestDir: string;
fSrcFiles: TStrings;
fFilesEvent: TEvent;
fShutdownEvent: TEvent;
protected
procedure Execute; override;
public
constructor Create(const ADestDir: string);
destructor Destroy; override;
procedure AddFile(const ASrcFileName: string);
function IsCopyingFiles: boolean;
end;
constructor TFileCopyThread.Create(const ADestDir: string);
begin
inherited Create(True);
fCS := TCriticalSection.Create;
fDestDir := IncludeTrailingBackslash(ADestDir);
fSrcFiles := TStringList.Create;
fFilesEvent := TEvent.Create(nil, True, False, '');
fShutdownEvent := TEvent.Create(nil, True, False, '');
Resume;
end;
destructor TFileCopyThread.Destroy;
begin
if fShutdownEvent <> nil then
fShutdownEvent.SetEvent;
Terminate;
WaitFor;
FreeAndNil(fFilesEvent);
FreeAndNil(fShutdownEvent);
FreeAndNil(fSrcFiles);
FreeAndNil(fCS);
inherited;
end;
procedure TFileCopyThread.AddFile(const ASrcFileName: string);
begin
if ASrcFileName <> '' then begin
fCS.Acquire;
try
fSrcFiles.Add(ASrcFileName);
fFilesEvent.SetEvent;
finally
fCS.Release;
end;
end;
end;
procedure TFileCopyThread.Execute;
var
Handles: array[0..1] of THandle;
Res: Cardinal;
SrcFileName, DestFileName: string;
begin
Handles[0] := fFilesEvent.Handle;
Handles[1] := fShutdownEvent.Handle;
while not Terminated do begin
Res := WaitForMultipleObjects(2, @Handles[0], False, INFINITE);
if Res = WAIT_OBJECT_0 + 1 then
break;
if Res = WAIT_OBJECT_0 then begin
while not Terminated do begin
fCS.Acquire;
try
if fSrcFiles.Count > 0 then begin
SrcFileName := fSrcFiles[0];
fSrcFiles.Delete(0);
end else
SrcFileName := '';
if SrcFileName = '' then
fFilesEvent.ResetEvent;
finally
fCS.Release;
end;
if SrcFileName = '' then
break;
DestFileName := fDestDir + ExtractFileName(SrcFileName);
CopyFile(PChar(SrcFileName), PChar(DestFileName), True);
end;
end;
end;
end;
function TFileCopyThread.IsCopyingFiles: boolean;
begin
fCS.Acquire;
try
Result := (fSrcFiles.Count > 0)
// last file is still being copied
or (WaitForSingleObject(fFilesEvent.Handle, 0) = WAIT_OBJECT_0);
finally
fCS.Release;
end;
end;
Чтобы использовать это в рабочем коде, вам необходимо добавить обработку ошибок, возможно, некоторые уведомления о ходе выполнения, и само копирование, вероятно, должно быть реализовано по-другому, но это должноНачало работы.
В ответ на ваши вопросы:
я должен создать FileCopyThread в FormCreate основной программы (и позволить ей работать),это как-то замедлит работу программы?
Вы можете создать поток, он будет блокировать события и использовать 0 циклов ЦП, пока вы не добавите файл для копирования.Как только все файлы будут скопированы, поток снова заблокируется, поэтому сохранение его на протяжении всего времени выполнения программы не оказывает отрицательного влияния, кроме использования некоторой памяти.
Можно ли добавить обычное уведомление о событии в FileCopyThread(так что я могу отправить событие как в свойстве onProgress: TProgressEvent read fOnProgressEvent write fOnProgressEvent; с fi - текущее число файлов в списке и файл, обрабатываемый в данный момент. Я хотел бы вызвать это при добавлении и до, и после копированиярутина
Вы можете добавлять уведомления, но для того, чтобы они были действительно полезными, их нужно выполнять в контексте основного потока. Самый простой и уродливый способ сделать это - обернуть их с помощью Synchronize()
метод. Посмотрите пример демонстрации Delphi Threads, как это сделать. Затем прочтите некоторые вопросы и ответы, найденные поиском «[delphi] synchronize» здесь в SO, чтобы увидеть, как этот метод имеет немало недостатков..
Однако я бы не стал реализовыватьуведомления таким образом.Если вы просто хотите отобразить прогресс, нет необходимости обновлять его с каждым файлом.Кроме того, у вас уже есть вся необходимая информация в потоке VCL, в месте, куда вы добавляете файлы для копирования.Вы можете просто запустить таймер с Interval
, скажем, 100, и обработчик событий таймера проверит, занят ли поток и сколько файлов осталось для копирования.Когда поток снова заблокирован, вы можете отключить таймер.Если вам требуется больше или другая информация из потока, вы можете легко добавить больше потоково-безопасных методов в класс потока (например, вернуть количество ожидающих файлов).Я начал с минимального интерфейса, чтобы все было легко и просто, используйте его только для вдохновения.
Прокомментируйте обновленный вопрос:
У вас есть этот код:
function TFileCopyThread.GetFileBeingCopied: string;
begin
Result := '';
if fFileBeingCopied <> '' then begin
fCS.Acquire;
try
Result := fFileBeingCopied;
fFilesEvent.SetEvent;
finally
fCS.Release;
end;
end;
end;
но есть две проблемы с этим.Во-первых, весь доступ к полям данных должен быть защищен, чтобы быть безопасным, а затем вы просто читаете данные, а не добавляете новый файл, поэтому нет необходимости устанавливать событие.Пересмотренный метод будет выглядеть так:
function TFileCopyThread.GetFileBeingCopied: string;
begin
fCS.Acquire;
try
Result := fFileBeingCopied;
finally
fCS.Release;
end;
end;
Также вы устанавливаете только поле fFileBeingCopied
, но никогда не сбрасываете его, поэтому оно всегда будет равно последнему скопированному файлу, даже когда поток заблокирован.Вы должны установить эту строку пустой, когда последний файл был скопирован, и, конечно, сделать это, пока критический раздел получен.Просто переместите задание за блок if
.