【问题标题】:Delphi7 - How can i copy a file that is being written toDelphi7 - 我如何复制正在写入的文件
【发布时间】:2011-06-08 22:17:23
【问题描述】:

我有一个应用程序,它在主 PC 上每秒将信息记录到每日文本文件中。网络上使用相同应用程序的从属 PC 想要将此文本文件复制到其本地驱动器。我可以看到会有文件访问问题。

这些文件每个不应大于 30-40MB。网络将是 100MB 以太网。我可以看到复制过程可能需要超过 1 秒的时间,这意味着记录 PC 需要在读取文件时打开文件进行写入。

文件写入(记录)和文件复制过程的最佳方法是什么?我知道有标准的 Windows CopyFile() 过程,但是这给了我文件访问问题。还有使用 fmShareDenyNone 标志的 TFileStream,但这也偶尔会给我带来访问问题(例如每周 1 次)。

完成这项任务的最佳方式是什么?

我当前的文件记录:

procedure FSWriteline(Filename,Header,s : String);
var LogFile : TFileStream;
line : String;
begin
     if not FileExists(filename) then
     begin
          LogFile := TFileStream.Create(FileName, fmCreate or fmShareDenyNone);
          try
             LogFile.Seek(0,soFromEnd);
             line := Header + #13#10;
             LogFile.Write(line[1],Length(line));
             line := s + #13#10;
             LogFile.Write(line[1],Length(line));
          finally
                 logfile.Free;
          end;
     end else begin
         line := s + #13#10;
         Logfile:=tfilestream.Create(Filename,fmOpenWrite or fmShareDenyNone);
         try
            logfile.Seek(0,soFromEnd);
            Logfile.Write(line[1], length(line));
         finally
            Logfile.free;
         end;
     end;
end;

我的文件复制过程:

procedure DoCopy(infile, Outfile : String);
begin
     ForceDirectories(ExtractFilePath(outfile)); //ensure folder exists
     if FileAge(inFile) = FileAge(OutFile) then Exit; //they are the same modified time
     try
        { Open existing destination }
        fo := TFileStream.Create(Outfile, fmOpenReadWrite or fmShareDenyNone);
        fo.Position := 0;
     except
           { otherwise Create destination }
           fo := TFileStream.Create(OutFile, fmCreate or fmShareDenyNone);
     end;
     try
        { open source }
        fi := TFileStream.Create(InFile, fmOpenRead or fmShareDenyNone);
        try
           cnt:= 0;
           fi.Position := cnt;
           max := fi.Size;
           {start copying }
           Repeat
                 dod := BLOCKSIZE; // Block size
                 if cnt+dod>max then dod := max-cnt;
                 if dod>0 then did := fo.CopyFrom(fi, dod);
                 cnt:=cnt+did;
                 Percent := Round(Cnt/Max*100);
           until (dod=0)
        finally
               fi.free;
        end;
     finally
            fo.free;
     end;
end;

【问题讨论】:

    标签: delphi file-io delphi-7 file-locking file-copying


    【解决方案1】:

    我建议不要一开始就反复关闭和​​重新打开共享文件。由于您每秒都在写入,这只是不必要的开销。

    在 Master 端,创建并关闭文件(fmCreate 标志不能与其他标志一起使用!),然后以fmOpenWrite 模式和fmShareDenyWrite 共享重新打开它,保持打开状态,然后写入需要的时候给它。

    在 Slave 端,以fmOpenRead 模式打开文件,fmShareDenyNone 共享,保持打开状态,每秒读取一次。无需每次都通过网络复制整个共享文件。那是浪费带宽。只需读取过去几秒钟内写入的任何新数据即可。如果 Slave 需要将数据存储在本地文件中,那么它可以独立于共享文件管理一个单独的本地文件,在需要时将新数据推送到本地文件中。

    【讨论】:

    • 对日志机制的好建议——我一定会实施的。在我的情况下,从站不需要每秒复制一次文件,而是每天一次 - 因此它在复制之前检查两个文件的修改时间。
    • re fmCreate:这是 TFileStream 类实现的一个缺点。 Windows API 允许在没有该解决方法的情况下创建带有标志的文件。我编写了自己的类来封装它:svn.berlios.de/svnroot/repos/dzchart/utilities/dzLib/trunk/src/…
    • 这个限制在以后的 VCL 版本中被移除了。 FileCreate() 和 TFileStream 的构造函数中添加了新参数,以允许同时指定访问权限和共享权限。
    【解决方案2】:

    处理您特定的偶尔反复出现的问题:

    你没有说你使用的是什么版本的 Delphi。

    TFileStream.Create() 构造函数中存在一个错误,直到并包括 2007 版(至少)。这或许可以解释您偶尔遇到的并发问题。

    话虽如此,我认为该错误更有可能导致文件未按预期创建(当另外指定 ShareMode 时),这可能反过来导致您的并发问题。

    解决这个问题的一种方法可能是当需要创建文件时,首先创建文件,然后简单地将其打开以作为单独的构造函数调用进行写入 - 这实际上使文件创建成为一个单独的步骤,文件写入是文件的一致部分进程:

      if not FileExists(filename) then
      begin
        // There may be a more efficient way of creating an empty file, but this 
        //  illustrates the approach
    
        LogFile := TFileStream.Create(FileName, fmCreate);
        LogFile.Free;
    
        line := Header + #13#10 + s + #13#10;
      end
      else
        line := s + #13#10;
    
      Logfile:=tfilestream.Create(Filename,fmOpenWrite or fmShareDenyNone);
      try
        logfile.Seek(0,soFromEnd);
        Logfile.Write(line[1], length(line));
      finally
        Logfile.free;
      end;
    

    【讨论】:

    • 使用Delphi7(它在标题中:))。我会调查这个创建错误。谢谢!
    • 啊,是的——我怎么没注意到?哈哈
    【解决方案3】:

    使用标准的Append文件创建/打开命令,使用write更新日志,并立即使用close文件。

    使用操作系统上的作业来复制/移动文件;让它重试并以高于您要求的频率启动。

    如果您想在 Delphi 中执行此操作,请使用 MoveFile 移动整个内容。

    您可能希望将日志写入和移动都包装在try-except 中,以便在文件系统(Windows 上的 NTFS?)无法为您解决并发问题时重试它们的合理次数。在最坏的情况下,要么:

    1. 文件被移动,它被重新创建和写入。
    2. 文件没有立即移动,因为它正在被写入。

    如果操作系统没有解决竞争条件,那么您将不得不使用信号量/锁优先处理饥饿的操作。

    【讨论】:

    • 这个答案包含许多不同的坏建议。
    • @david - 同意。使用 TFile 是我尝试的第一件事,并立即导致问题。
    猜你喜欢
    • 2013-10-02
    • 1970-01-01
    • 2011-12-13
    • 1970-01-01
    • 2013-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多