Получение текущей длины файла /FileInfo.Length кэширования и устаревшей информации

Я отслеживаю папку с файлами и их длину файла, по крайней мере один из этих файлов по-прежнему записывается.

Мне нужно постоянно обновлять запись каждой длины файла, которую я использую для других целей.

Метод 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 if это важно).

Текущее обходное решение - вызвать fi.Refresh(), чтобы принудительно обновить информацию о каждом файле. Это внутренне, кажется, делегирует вызов GetFileAttributesEx Win32 API для обновления информации о файле.

Хотя затраты на принудительное обновление вручную допустимы, я предпочел бы понять, почему я получаю устаревшую информацию в первую очередь. Когда генерируется информация о FileInfo и как она связана с вызовом DirectoryInfo.GetFiles()? Существует ли слой кэширования ввода-вывода файлов, который я не полностью понимаю?

Ответ 1

Раймонд Чен теперь написал очень подробное сообщение в блоге об этой проблеме:

Почему размер файла указан неверно для файлов, которые все еще записываются?

В NTFS метаданные файловой системы - это свойство не записи каталога а скорее файла, причем некоторые из метаданных, запись в качестве настройки для улучшения перечисления каталога производительность. Такие функции, как FindFirstFile, сообщают каталог и, добавив метаданные, к которым пользователи FAT привыкли получать "бесплатно", они могли бы избежать медленного, чем FAT для списки каталогов. Функции перечисления-каталога сообщают обновленные метаданные, которые могут не соответствовать реальным метаданным если запись в каталоге устарела.

По сути, это сводится к производительности: информация о каталоге, собранная из DirectoryInfo.GetFiles() и FindFirstFile/FindNextFile Win32 API снизу, кэшируется по причинам производительности, чтобы гарантировать лучшую производительность в NTFS, чем в старой FAT для получения информации о каталоге, Точную информацию о размере файла можно получить, вызвав Get­File­Size() непосредственно в файле (в .NET call Refresh() на FileInfo или получить FileInfo из имени файла напрямую) - или открыть и закрыть поток файлов что приводит к распространению обновленной информации о файлах в кеш метаданных каталога. В последнем случае объясняется, почему размер файла сразу обновляется, когда процесс записи закрывает файл.

Это также объясняет, что проблема, казалось бы, не отображалась в Windows 2003 Server - тогда информация о файле чаще повторялась/всякий раз, когда кеш был очищен - это больше не имеет отношения к Windows Server 2008:

Как часто, ответ немного сложнее. Начиная с Windows Vista (и соответствующая версия Windows Server, для которой я не знаю, но я уверен, что вы можете посмотреть, а "вы" я имею в виду "Yuhong Бао" ), файловая система NTFS выполняет эту репликацию вежливости, когда последний дескриптор файлового объекта закрыт. Более ранние версии NTFS реплицировал данные, пока файл был открыт, когда кеш был покрасневшее, что означало, что это происходило так часто в соответствии с непредсказуемый график. Результатом этого изменения является то, что запись каталога теперь обновляется реже, и, следовательно, последний обновленный размер файла более устаревший, чем он уже был.

Чтение полной статьи очень информативно и рекомендуется!

Ответ 2

I thik, что вы должны использовать FileSystemWatcher и подписаться на Измененное событие. Он запускается при изменении указанного элемента файловой системы.

Ответ 3

Я согласен с Wojteq в том, что использование класса FileSystemWatcher будет лучшим решением. Он раскрывает события, когда изменяются различные атрибуты файла или каталога (например, событие изменения, на которое он ссылается), и это лучшее решение, чем решение опроса, которое в настоящее время существует. Чтобы ответить на ваш вопрос о том, почему Refresh принимает переменные количества времени, чтобы отразить изменение размера файла, ответ заключается в том, что он связан с базовым диспетчером виртуальной памяти операционной системы Windows. Когда выполняется ввод/вывод файлов, он фактически обновляет файлы с отображением памяти; это буферная копия файла, управляемого операционной системой. Таким образом, Windows управляет, когда буферные данные записываются на диск. Невозможно предсказать, когда конкретная часть буферизованных данных будет физически записана на диск. Это означает, что обновление потока файлов будет содержать эти обновления в буфере. Если вы были в потоке Flush(), буферизованные обновления должны быть немедленно записаны на диск, если вы закроете поток, тогда он будет записан из буфера на диск сразу после закрытия потока, и если поток будет открыт, он будет открыт для Windows, когда он решает записать на диск буферизованные данные.