Копирование файлов, которые основной поток добавляет в список строк, используя поток - PullRequest
3 голосов
/ 17 января 2010

У меня есть программа для создания веб-сайтов, которая при создании сайта создает сотни файлов.

Когда корневая папка Интернета расположена на локальном компьютере, программа работает нормально. Если корневая папка Интернета расположена на сетевом диске, копирование созданной страницы занимает больше времени, чем создание самой страницы (создание страницы довольно оптимизировано).

Я думал о том, чтобы создать файлы локально, добавить имена созданных файлов в TStringList и позволить другому потоку скопировать их на сетевой диск (удалив скопированный файл из TStringList).

Однако, я никогда раньше не использовал потоки, и я не смог найти существующего ответа в других вопросах Delphi, касающихся потоков (, если только мы могли бы использовать оператор and в поле поиска ), поэтому я сейчас спрашиваю, есть ли у кого-нибудь рабочий пример, который делает это (или может указать мне какую-нибудь статью с рабочим кодом Delphi)?

Я использую Delphi 7.

РЕДАКТИРОВАНИЕ: Мой пример проекта (спасибо исходному коду mghie - который еще раз благодарит).

  ...
  fct : TFileCopyThread;
  ...

  procedure TfrmMain.FormCreate(Sender: TObject);
  begin
     if not DirectoryExists(DEST_FOLDER)
     then
        MkDir(DEST_FOLDER);
     fct := TFileCopyThread.Create(Handle, DEST_FOLDER);
  end;


  procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
  begin
     FreeAndNil(fct);
  end;

  procedure TfrmMain.btnOpenClick(Sender: TObject);
  var sDir : string;
      Fldr : TedlFolderRtns;
      i : integer;
  begin
     if PickFolder(sDir,'')
     then begin
        // one of my components, returning a filelist [non threaded  :) ] 
        Fldr := TedlFolderRtns.Create();
        Fldr.FileList(sDir,'*.*',True);
        for i := 0 to Fldr.TotalFileCnt -1 do
        begin
           fct.AddFile( fldr.ResultList[i]);
        end;
     end;
  end;

  procedure TfrmMain.wmFileBeingCopied(var Msg: Tmessage);
  var s : string;
  begin
     s := fct.FileBeingCopied;
     if s <> ''
     then
        lbxFiles.Items.Add(fct.FileBeingCopied);
     lblFileCount.Caption := IntToStr( fct.FileCount );
  end;

и единица

  unit eFileCopyThread;
  interface
  uses
     SysUtils, Classes, SyncObjs, Windows, Messages;
  const
    umFileBeingCopied = WM_USER + 1;
  type

    TFileCopyThread = class(TThread)
    private
      fCS: TCriticalSection;
      fDestDir: string;
      fSrcFiles: TStrings;
      fFilesEvent: TEvent;
      fShutdownEvent: TEvent;
      fFileBeingCopied: string;
      fMainWindowHandle: HWND;
      fFileCount: Integer;
      function GetFileBeingCopied: string;
    protected
      procedure Execute; override;
    public
      constructor Create(const MainWindowHandle:HWND; const ADestDir: string);
      destructor Destroy; override;

      procedure AddFile(const ASrcFileName: string);
      function IsCopyingFiles: boolean;
      property FileBeingCopied: string read GetFileBeingCopied;
      property FileCount: Integer read fFileCount;
    end;

  implementation
  constructor TFileCopyThread.Create(const MainWindowHandle:HWND;const ADestDir: string);
  begin
    inherited Create(True);
    fMainWindowHandle := MainWindowHandle;
    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);
        fFileCount := fSrcFiles.Count;
        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);
              fFileCount := fSrcFiles.Count;
              PostMessage( fMainWindowHandle,umFileBeingCopied,0,0 );
           end else
               SrcFileName := '';
           fFileBeingCopied := 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; 

  // new version - edited after receiving comments 
  function TFileCopyThread.GetFileBeingCopied: string; 
  begin 
     fCS.Acquire; 
     try 
        Result := fFileBeingCopied; 
     finally 
        fCS.Release; 
     end; 
  end; 

  // old version - deleted after receiving comments 
  //function TFileCopyThread.GetFileBeingCopied: string;
  //begin
  //  Result := '';
  //  if fFileBeingCopied <> ''
  //  then begin
  //    fCS.Acquire;
  //    try
  //      Result := fFileBeingCopied;
  //      fFilesEvent.SetEvent;
  //    finally
  //      fCS.Release;
  //    end;
  //  end;
  //end;

  end.

Будем весьма благодарны за любые дополнительные комментарии.

Читая комментарии и просматривая примеры, вы найдете разные подходы к решениям, с за и против комментариями по всем из них.

Проблема при попытке реализовать сложную новую функцию (как мне кажется, темы) состоит в том, что вы почти всегда находите что-то, что, кажется, работает ... сначала. Только позже вы узнаете, каким трудным образом все должно было быть иначе. И темы являются очень хорошим примером этого.

Такие сайты, как StackOverflow, великолепны. Что за сообщество.

Ответы [ 3 ]

12 голосов
/ 17 января 2010

Быстрое и грязное решение:

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.

2 голосов
/ 17 января 2010

Если вы несколько неохотно спускаетесь к металлу и имеете дело с TThread напрямую, как в mghie solution , альтернативой, возможно, более быстрой, является использование Andreyn Hausladen * AsyncCalls .

код скелета:

procedure MoveFile(AFileName: TFileName; const DestFolder: string);
//------------------------------------------------------------------------------
begin
  if DestFolder > '' then
    if CopyFile(PChar(AFileName), PChar(IncludeTrailingPathDelimiter(DestFolder) + ExtractFileName(AFileName)), False) then
      SysUtils.DeleteFile(AFileName)
    else
      RaiseLastOSError;
end;

procedure DoExport;
//------------------------------------------------------------------------------
var
  TempPath, TempFileName: TFileName;
  I: Integer;
  AsyncCallsList: array of IAsyncCall;
begin
  // find Windows temp directory
  SetLength(TempPath, MAX_PATH);
  SetLength(TempPath, GetTempPath(MAX_PATH, PChar(TempPath)));

  // we suppose you have an array of items (1 per file to be created) with some info
  SetLength(AsyncCallsList, Length(AnItemListArray));
  for I := Low(AnItemListArray) to High(AnItemListArray) do
  begin
    AnItem := AnItemListArray[I];
    LogMessage('.Processing current file for '+ AnItem.NAME);
    TempFileName := TempPath + Format(AFormatString, [AnItem.NAME, ...]);
    CreateYourFile(TempFileName);
    LogMessage('.File generated for '+ AnItem.NAME);
    // Move the file to Dest asynchronously, without waiting
    AsyncCallsList[I] := AsyncCall(@MoveFile, [TempFileName, AnItem.DestFolder])
  end;

  // final rendez-vous synchronization
  AsyncMultiSync(AsyncCallsList);
  LogMessage('Job finished... ');
end;
1 голос
/ 17 января 2010

Хорошее начало для использования темы - Delphi находится на Delphi о сайте

Чтобы ваше решение работало, вам нужна очередь заданий для рабочего потока. Можно использовать список строк. Но в любом случае вам нужно защищать очередь, чтобы в нее мог писать только один поток. Даже если поток записи приостановлен.

Ваше приложение записывает в очередь. Таким образом, должен быть защищенный метод записи.

Ваш поток читает и удаляет из очереди. Так что должен быть защищенный метод чтения / удаления.

Вы можете использовать критический раздел, чтобы убедиться, что только один из них имеет доступ к очереди в любой момент.

...