【问题标题】:Error: "Cannot open file "20210609.log". The process cannot access the file because it is being used by another process"错误:“无法打开文件“20210609.log”。该进程无法访问该文件,因为它正被另一个进程使用”
【发布时间】:2021-06-16 18:29:45
【问题描述】:

我有一些第三方代码使用以下代码一次写入日志文件一行:

procedure TLog.WriteToLog(Entry: ansistring);
var
    strFile: string;
    fStream: TFileStream;
    strDT: ansistring;
begin
    if ((strLogDirectory<>'') and (strFileRoot<>'')) then
    begin
        if not(DirectoryExists(strLogDirectory)) then
            ForceDirectories(strLogDirectory);
        strFile:=strLogDirectory + '\' + strFileRoot + '-' + strFilename;
        if FileExists(strFile) then
            fStream:=TFileStream.Create(strFile, fmOpenReadWrite)
        else
            fStream:=TFileStream.Create(strFile, fmCreate);
        fStream.Seek(0, soEnd);
        if blnUseTimeStamp then
            strDT:=formatdatetime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + chr(13) + chr(10)
        else
            strDT:=Entry + chr(13) + chr(10);
        fStream.WriteBuffer(strDT[1], length(strDT));
        FreeandNil(fStream);
    end;
end;

这以前在客户站点上运行良好,但在过去几周中,它现在在标题中出现错误。

没有其他进程应该打开文件。我怀疑它是防病毒软件,但客户声称他们已禁用防病毒软件,但仍然收到错误消息。

该错误似乎只发生在代码处于循环中并快速写入行时。

我想知道的: 假设不是防病毒软件(或类似软件)导致问题,是否是由于操作系统在下次尝试写入文件之前未清除标志(或类似软件)?

【问题讨论】:

  • 您缺少TFileStream 构造函数(即fmShareDenyNone 等)的共享权限,您还应该使用try..finally 保护TFileStream。在ForceDirectories() 之前不需要DirectoryExists(),它会在内部检查是否存在。
  • IIRC,如果文件不存在,fmOpenReadWrite 不会创建文件吗?我得检查一下……
  • 通常不会,但除了 AV 之外,可能还有其他扫描进程试图访问您的文件。其次,当某行写入失败时,正如雷米所说,您不会释放文件流并保持文件锁定。
  • @Toby 是的,这很重要,这就是我之前提到fmShareDenyNone 的原因。例如:fStream := TFileStream.Create(strFile, fmOpenReadWrite or fmShareDenyNone);我问这是否可能是因为它经常快速地打开和关闭文件” - 不,这不是问题。该文件还有另一个打开的句柄,并且该句柄指定的共享权限(未指定)与此TFileStream 请求的权限不匹配。使用SysInternals Process Explorer 之类的工具进行验证。
  • @Toby 是的,确实如此:“Mode 参数由一个开放模式和(可能)一个共享模式或'ed 在一起组成。。 i>"

标签: windows delphi delphi-10.2-tokyo


【解决方案1】:

我想知道的:假设不是防病毒软件(或类似软件)导致了问题,可能是因为操作系统在下次尝试写入之前没有清除标志(或类似的东西)到文件?

不,这不是速度问题,也不是缓存问题。这是共享违规,这意味着同一文件必须有另一个打开的句柄,其中该句柄分配(或缺少)与此代码请求的权限不兼容的共享权限。

例如,如果另一个句柄不共享读+写访问权限,则在使用fmOpenReadWrite 创建TFileStream 时,此代码将无法打开文件。如果任何句柄对文件打开,则此代码将在使用fmCreate 创建TFileStream 时失败,因为默认情况下请求独占 访问该文件。

我会建议更像这样的东西:

procedure TLog.WriteToLog(Entry: AnsiString);
var
  strFile: string;
  fStream: TFileStream;
  strDT: AnsiString;
  fMode: Word;
begin
  if (strLogDirectory <> '') and (strFileRoot <> '') then
  begin
    ForceDirectories(strLogDirectory);
    strFile := IncludeTrailingPathDelimiter(strLogDirectory) + strFileRoot + '-' + strFilename;
    fMode := fmOpenReadWrite or fmShareDenyWrite;
    if not FileExists(strFile) then fMode := fMode or fmCreate;
    fStream := TFileStream.Create(strFile, fMode);
    try
      fStream.Seek(0, soEnd);
      if blnUseTimeStamp then
        strDT := FormatDateTime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + sLineBreak
      else
        strDT := Entry + sLineBreak;
      fStream.WriteBuffer(strDT[1], Length(strDT));
    finally
      fStream.Free;
    end;
  end;
end;

但是,请注意使用FileExists() 会引入竞争条件。在检查存在之后和打开/创建文件之前,其他人可能会删除/创建该文件。最好让操作系统为您处理。

至少在 Windows 上,您可以直接将 CreateFile()OPEN_ALWAYS 标志一起使用(TFileStream 只使用 CREATE_ALWAYSCREATE_NEWOPEN_EXISTING),然后分配生成的 THandleTHandleStream,例如:

procedure TLog.WriteToLog(Entry: AnsiString);
var
  strFile: string;
  hFile: THandle;
  fStream: THandleStream;
  strDT: AnsiString;
begin
  if (strLogDirectory <> '') and (strFileRoot <> '') then
  begin
    ForceDirectories(strLogDirectory);
    strFile := IncludeTrailingPathDelimiter(strLogDirectory) + strFileRoot + '-' + strFilename;
    hFile := CreateFile(PChar(strFile), GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_ALWAYS, 0, 0);
    if hFile = INVALID_HANDLE_VALUE then RaiseLastOSError;
    try
      fStream := THandleCreate.Create(hFile);
      try
        fStream.Seek(0, soEnd);
        if blnUseTimeStamp then
          strDT := FormatDateTime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + sLineBreak
        else
          strDT := Entry + sLineBreak;
        fStream.WriteBuffer(strDT[1], Length(strDT));
      finally
        fStream.Free;
      end;
    finally
      CloseHandle(hFile);
    end;
  end;
end;

在任何情况下,您都可以使用SysInternals Process Explorer 之类的工具来验证文件是否还有另一个句柄打开,以及它属于哪个进程。如果有问题的句柄在您在 PE 中看到它之前已被关闭,则使用SysInternals Process Monitor 之类的工具实时记录对文件的访问并检查打开文件的重叠尝试。

【讨论】:

  • 调用 WriteToLog 的代码有一个异常处理程序,该处理程序也调用 WriteTolog 8-) 并且它能够写入异常 - 所以无论它是什么,它都不会长时间锁定文件。我使用过 Process Explorer(喜欢 ProcExp),但什么也看不到。不知道进程监视器。会告诉他们试试看!
  • 一如既往 - 非常有帮助! (和病人)
猜你喜欢
  • 1970-01-01
  • 2010-12-10
相关资源
最近更新 更多