代码之家  ›  专栏  ›  技术社区  ›  Edelcom

使用线程复制主线程添加到字符串列表的文件

  •  3
  • Edelcom  · 技术社区  · 15 年前

    我有一个网页创建程序,当建立一个网站,创建数百个文件。

    当Internet根文件夹位于本地PC上时,程序运行正常。如果Internet根文件夹位于网络驱动器上,则复制创建的页面比创建页面本身花费的时间更长(页面的创建相当优化)。

    我正在考虑在本地创建文件,将创建的文件的名称添加到tstringlist,并让另一个线程将其复制到网络驱动器(从tstringlist中删除复制的文件)。

    但是,我以前从未使用过线程,在其他涉及线程的Delphi问题中我找不到现有的答案。( 如果我们能用 and 搜索字段中的运算符 ,所以我现在要问是否有人有一个工作的例子,它是这样做的(或者可以向我指出一些使用Delphi代码的文章)?

    我用的是Delphi7。

    编辑:我的示例项目(THX到原始代码 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 回复  |  直到 15 年前
        1
  •  12
  •   mghie    15 年前

    快速而肮脏的解决方案:

    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(并让它运行),这会以某种方式减慢程序的运行速度?

    您可以创建线程,它将阻塞事件并消耗0个CPU周期,直到您添加要复制的文件。一旦复制了所有文件,线程将再次阻塞,因此在程序的整个运行时保持线程除了消耗一些内存外没有任何负面影响。

    我可以向filecopythread添加常规事件通知吗(这样我就可以在属性onprogress中发送事件:tprogressevent read f onprogress event write f onprogress event;使用f.i.列表中的当前文件数以及当前处理的文件。我想在添加和复制例程前后调用这个

    您可以添加通知,但要使它们真正有用,它们需要在主线程上下文中执行。最简单和最丑的方法是用 Synchronize() 方法。查看Delphi线程演示,了解如何执行此操作的示例。然后阅读通过在这里搜索“[delphi]synchronize”找到的一些问题和答案,看看这种技术有哪些缺点。

    但是,我不会以这种方式实现通知。如果您只想显示进度,则不需要用每个文件更新此进度。另外,您已经在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
  •  2
  •   Community CDub    8 年前

    如果你有点不愿意深入到金属,直接处理螺纹,就像在 mghie solution ,另一种可能更快的方法是使用 Andreas Hausladen's 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;
    
        3
  •  1
  •   Toon Krijthe    15 年前

    使用线程的一个好的开始是delphi the Delphi about site

    为了使您的解决方案工作,您需要一个工作线程的作业队列。可以使用字符串列表。但在任何情况下,您都需要保护队列,以便在任何时刻只有一个线程可以写入队列。即使写入线程被挂起。

    您的应用程序将写入队列。所以必须有一个有保护的写方法。

    线程读取并从队列中删除。因此,必须有一个受保护的读取/删除方法。

    您可以使用关键部分来确保在任何时刻只有其中一个可以访问队列。