【问题标题】:getting current file length / FileInfo.Length caching and stale information获取当前文件长度 / FileInfo.Length 缓存和陈旧信息
【发布时间】:2011-10-19 21:06:31
【问题描述】:

我正在跟踪一个文件夹及其文件长度,其中至少有一个文件仍在被写入。

我必须不断更新我用于其他目的的每个文件长度的记录。

Update 方法每 15 秒调用一次,如果文件长度与上次更新中确定的长度不同,则更新文件的属性。

更新方法如下所示:

var directoryInfo = new DirectoryInfo(archiveFolder);
var archiveFiles = directoryInfo.GetFiles()
                                .OrderByDescending(f=>f.CreationTimeUtc); 
foreach (FileInfo fi in archiveFiles)
{
    //check if file existed in previous update already
    var origFileProps = cachedFiles.GetFileByName(fi.FullName);
    if (origFileProps != null && fi.Length == origFileProps.EndOffset)
    {
        //file length is unchanged
    }
    else
    {
        //Update the properties of this file
        //set EndOffset of the file to current file length
    }
}

我知道DirectoryInfo.GetFiles() 正在预填充许多FileInfo 属性,包括Length - 只要在更新之间 之间不进行缓存,这是可以的(缓存信息不应超过 15 秒)。

我假设每个DirectoryInfo.GetFiles() 调用都会生成一组 FileInfos,然后使用FindFirstFile/FindNextFile Win32 API 填充新信息。但这似乎并非如此。

很少,但最终肯定会遇到这样的情况,即写入的文件的文件长度一次没有更新 5、10 甚至 20 分钟(测试在 Windows 2008 Server x64 上完成,如果这很重要)。

当前的解决方法是调用fi.Refresh() 来强制更新每个文件信息。这在内部似乎委托给GetFileAttributesEx Win32 API 调用来更新文件信息。

虽然手动强制刷新的成本是可以容忍的,但我宁愿理解为什么我首先会得到陈旧的信息。 FileInfo 信息是何时生成的,它与 DirectoryInfo.GetFiles() 的调用有何关系?下面是否有我没有完全掌握的文件 I/O 缓存层?

【问题讨论】:

    标签: c# .net file-io


    【解决方案1】:

    Raymond Chen 现在就这个问题写了一篇非常详细的博文:

    Why is the file size reported incorrectly for files that are still being written to?

    在 NTFS 中,文件系统元数据是不属于目录条目的属性 而是文件,将一些元数据复制到 目录条目作为改进目录枚举的调整 性能。 FindFirstFile 等函数报告目录 条目,并通过放置 FAT 用户习惯的元数据 获得“免费”,他们可以避免比 FAT 慢 目录列表。 目录枚举函数报告 最后更新的元数据,可能与实际元数据不对应 如果目录条目过时。

    本质上归结为性能:从DirectoryInfo.GetFiles() 和下面的FindFirstFile/FindNextFile Win32 API 收集的目录信息出于性能原因被缓存,以保证在 NTFS 中比在旧 FAT 中获取目录信息的性能更好.准确的文件大小信息只能通过直接对文件调用Get­File­Size()(在.NET 中对FileInfo 调用Refresh() 或直接从文件名获取FileInfo)或打开和关闭文件流来获取这会导致更新的文件信息传播到目录元数据缓存。后一种情况解释了为什么在写入过程关闭文件时文件大小会立即更新。

    这也解释了该问题似乎没有出现在 Windows 2003 Server 中 - 那时文件信息被更频繁地复制/每当缓存被刷新时 - Windows 2008 Server 不再是这种情况:

    至于多久,答案有点复杂。开始于 Windows Vista(及其相应的 Windows Server 版本,我 不知道,但我相信你可以抬头看,“你”是指“雨红” Bao"),NTFS 文件系统在执行此礼貌复制时 文件对象的最后一个句柄已关闭。 早期版本的 NTFS 在文件打开时复制数据,只要缓存是 脸红了,这意味着它经常发生 不可预知的时间表。这种变化的结果是 目录条目现在更新频率降低,因此 上次更新的文件大小比以前更过时。

    阅读全文信息量很大,值得推荐!

    【讨论】:

      【解决方案2】:

      我认为您应该使用 FileSystemWatcher 并订阅 Changed 事件。当指定的文件系统项改变时触发。

      【讨论】:

      • +1 提出一个明智的建议——此时我确实有一个解决方法,从长远来看,很可能会重构为使用 FileSystemWatcher——但这并不能回答 why i> 我得到的是陈旧的信息,这就是这个问题的全部意义
      • IMO 那里必须有一些操作系统兑现层。即使您调用 stream.Flush() 它也不会强制保存在 HD 上。您是否尝试过禁用磁盘写入缓存? support.microsoft.com/kb/259716
      • 在没有任何其他解释的情况下,我暂时接受这个答案作为解决方法。
      【解决方案3】:

      我同意 Wojteq 的观点,即使用 FileSystemWatcher 类会是更好的解决方案。它公开了文件或目录的不同属性发生变化时的事件(例如他引用的 Change 事件),它是比当前使用的轮询解决方案更好的解决方案。要回答您关于为什么刷新需要不同时间来反映文件大小变化的问题,答案是与 Windows 操作系统的底层虚拟内存管理器有关。当执行文件 I/O 时,它实际上会针对内存映射文件进行更新;这是由操作系统管理的文件的缓冲副本。因此,Windows 控制缓冲数据何时写入磁盘。无法预测何时将特定的缓冲数据物理写入磁盘。这意味着更新文件流会将这些更新放在缓冲区中。如果您要 Flush() 流,则缓冲的更新应立即写入磁盘,如果您关闭流,则它将在流关闭后立即从缓冲区写入磁盘,如果流保持打开状态,则它已启动当 Windows 决定将缓冲的数据写入磁盘时。

      【讨论】:

      • 缓冲将解释更新延迟以秒而不是分钟为单位,编写代码是在 C 中并使用 fwrite,默认情况下使用不超过几千字节的缓冲区大小 AFAIK。
      • +1 肯定与仍在写入的文件有关,我注意到一旦写入过程停止,我会立即得到正确的更新。但是,过时信息问题很少发生,这仍然引出了一个问题为什么首先会发生这种情况
      猜你喜欢
      • 2020-03-01
      • 1970-01-01
      • 2016-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多