【问题标题】:Renaming file in TTimer event produces an error在 TTimer 事件中重命名文件会产生错误
【发布时间】:2019-12-18 22:18:00
【问题描述】:

请参阅以下如何重现的步骤。我使用 Delphi 10.1 Berlin 和 Windows 10 并编译为 win32。

  1. 创建新的 VCL 表单应用程序
  2. 在表单上放置一个 TTimer 和一个 TMemo
  3. 将计时器的间隔设置为 10 毫秒
  4. 将此代码放入 OnTimer 事件中:
if FileExists('named.txt') then
begin
  Memo1.Lines.Add('named.txt exists');
  DeleteFile('renamed.txt');  //delete if it exists
  if RenameFile('named.txt', 'renamed.txt') then
    Memo1.Lines.Add(' renamed OK')
  else
    Memo1.Lines.Add(' rename failed with error : '+ IntToStr(GetLastError));
end;
  1. 运行程序

  2. 创建一个名为.txt的文件

TMemo 输出显示:

named.txt 存在 改名OK
  1. 现在在资源管理器中将文件renamed.txt 重命名为named.txt。

TMemo 输出现在显示:

named.txt 存在 改名OK named.txt 存在 改名OK

但是会出现一条错误消息,显示“文件或文件夹不存在”。为什么?

(重命名文件返回 OK)。

将计时器的间隔设置为例如 500 毫秒似乎没问题(没有错误消息)。

这是消息(瑞典语):

我什至将 exe 文件复制到另一台 PC,结果相同:

【问题讨论】:

  • 用另一组文件名试一试——但在这个小测试程序中——看看你是否能重现问题。如果您与另一个进程处于竞争状态,那么使用其他文件名将消除来自该其他进程的交互,并且您可以查看问题是否可重现......您的测试中可能已经有不同的文件名(从使用的名称来看,它可能看起来如此)。您是否尝试过使用此 exact 代码?
  • @RemyLebeau 我建议不要在打开包含要处理的数据的现有文件时使用FILE_FLAG_DELETE_ON_CLOSE 标志。为什么?使用此类标志意味着文件将在其所有句柄关闭后立即被删除。因此,如果由于某种原因,您的应用程序崩溃并且一旦关闭,您的应用程序将无法正确处理数据,则该文件的打开文件句柄将被关闭并删除文件,从而删除所有数据。应用程序应仅在正确处理此类文件时才关闭此类文件,而不是在此之前...
  • ... 在处理由应用程序创建的临时文件时,使用FILE_FLAG_DELETE_ON_CLOSE 更为常见,因为它确保即使应用程序崩溃,它们也会被删除,因此即使是不稳定的应用程序不会通过创建和从不删除一堆临时文件来慢慢消耗整个驱动器空间。
  • @HeartWare:感谢所有 cmets 和帮助。我更改了名称,但得到了同样的错误。我什至将 exe 文件复制到另一台 PC(到 c:\test)并得到相同的错误:“文件或文件夹不存在”。
  • @Thomas 病毒扫描程序锁定文件怎么样(当两台 PC 具有相同...)

标签: delphi ttimer


【解决方案1】:

我遇到了同样的问题,

首先:它似乎与您的代码无关。

据我所知,错误是 Windows 错误,当您尝试在第一个重命名过程(从 Windows)正确完成之前再次重命名文件时,会发生该错误,因此 delphi 重命名过程可能已经完成并且您的代码继续(并开始另一个重命名)但 Windows 重命名尚未正确完成。

当您使用不同的编程语言(例如批处理文件)时,也会发生同样的问题:

:loop
    ren named.txt renamed.txt
goto loop

您会收到相同的错误消息。

要解决您的问题,我唯一能想到的就是像您在帖子中建议的那样增加计时器延迟。

希望对你有帮助,很抱歉我无法解决您的问题

【讨论】:

  • 谢谢,很高兴知道至少有一个人可以重现该错误。我的外行猜测是它更有可能发生在“快速”系统上。我的测试是在带有 ssd 磁盘的“高端”cpu I7-6900K 上进行的。而且它并不总是发生错误。无论如何,Renamefile 返回 OK 但 Windows 报告错误仍然让我感到困惑。再次感谢您的评论。
【解决方案2】:

我的猜测是计时器事件中的一行最终会调用 Application.ProcessMessages(可能是添加到 Memo.Lines 属性)。如果距离定时器开始执行事件的时间已经超过10ms,那么消息队列中将会有一个新的定时器事件等待,这将再次触发对该事件的调用。

本质上,您正在执行以下语句:

  if FileExists('named.txt') then
  begin
    memo1.Lines.Add('named.txt exists');
    // Embedded ProcessMessages at some point leads to
    // the timer event being called again
    if FileExists('named.txt') then
    begin
      DeleteFile('renamed.txt');  //delete if it exists
      if RenameFile('named.txt', 'renamed.txt') then
        memo1.Lines.Add(' renamed OK')
      else
        memo1.Lines.Add(' rename failed with error : '+IntToStr(GetLastError));
    end;
    // Nested Timer Event could end here,
    // which returns execution to the outer event
    DeleteFile('renamed.txt');  //delete if it exists
    // The named.text file does not exist anymore - renamed away 7 lines above
    if RenameFile('named.txt', 'renamed.txt') then
      memo1.Lines.Add(' renamed OK')
    else
      memo1.Lines.Add(' rename failed with error : '+ IntToStr(GetLastError));
  end;
  // Original Timer event ends here...

一个解决方案是这样的:

Timer.Enabled := False;
try
  if FileExists('named.txt') then
  begin
    memo1.Lines.Add('named.txt exists');
    DeleteFile('renamed.txt');  //delete if it exists
    if RenameFile('named.txt', 'renamed.txt') then
      memo1.Lines.Add(' renamed OK')
    else
      memo1.Lines.Add(' rename failed with error : '+ IntToStr(GetLastError));
  end;
finally
  Timer.Enabled := True
end;

确保在您处理一个计时器事件时不会发生新的计时器事件。

【讨论】:

  • Memo1.Lines.Add 不抽取消息队列
  • 某事可能会发生 - 也许是 OP 未显示的某事。否则,他所描述的似乎是不可能的......如果他尝试了建议的解决方案并且它解决了问题,那么肯定会有所作为。
  • @Thomas:然后发生了一些奇怪的事情,很可能是在提供的代码外部。仅使用显示的代码,您描述的内容应该是不可能的。这也得到了 Tom 无法重现该问题的支持...
  • @HeartWare:程序运行时在资源管理器窗口中将“renamed.txt”重命名为“named.txt”,难道不能重现吗?我一直收到这个错误:i.stack.imgur.com/ZR76G.png
  • @HeartWare:我同意您的反重入措施,但认为还有更多内容 - 请参阅我对 q 的评论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-22
  • 1970-01-01
  • 2018-07-08
  • 2019-08-01
  • 2022-01-14
  • 2012-09-04
  • 1970-01-01
相关资源
最近更新 更多