2011-10-19 71 views
15

我跟蹤文件及其文件長度的文件夾,至少其中一個文件仍在寫入。獲取當前文件長度/ FileInfo.Length緩存和陳舊信息

我必須保持每個文件長度不斷更新的記錄,我用於其他目的。

如果文件長度與上一次更新中確定的長度不同,則每15秒調用一次Update方法並更新文件的屬性。

update方法看起來是這樣的:

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緩存層,我沒有完全掌握?

回答

14

雷蒙德陳現在已經寫了關於正是這個問題有很詳細的博客文章:

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

在NTFS,文件系統元數據是不是目錄條目 的屬性,而是的文件,將一些元數據複製到 目錄條目中作爲調整,以改善目錄枚舉 性能。像FindFirstFile這樣的功能報告目錄 條目,並且通過將FAT用戶習慣的元數據放在 獲得「免費」,他們可以避免比 目錄列表慢。 目錄枚舉函數報告 最後更新的元數據,如果目錄條目陳舊,則可能與實際元數據 不一致。

基本上可以歸結爲業績:從DirectoryInfo.GetFiles()收集的目錄信息和FindFirstFile/FindNextFile Win32 API的下方被緩存性能方面的考慮,以保證在NTFS比FAT老獲取目錄信息更好的性能。只能通過直接在文件上調用Get­File­Size()(在.NET中調用Refresh()FileInfo或直接從文件名中獲取FileInfo)來獲取準確的文件大小信息 - 或打開和關閉導致更新的文件信息爲傳播到目錄元數據緩存。後面的例子解釋了爲什麼在寫入過程關閉文件時立即更新文件大小。

這也解釋說,這個問題似乎並沒有在Windows中顯示2003服務器 - 當時的文件信息被複制更多的時候/每當高速緩存刷新 - 這是不一樣了適用於Windows 2008服務器:

至於多久一次,答案會稍微複雜一點。從 開始Windows Vista(及其相應的Windows服務器版本,其中我不知道不知道,但我確定您可以查找,而「you」我的意思是「Yuhong Bao」),NTFS文件系統執行此禮貌當 文件對象的最後一個句柄關閉時進行復制。 早期版本的NTFS 在文件打開時複製數據,只要緩存是 刷新的,這意味着根據不可預知的時間表,它每隔一段時間就會發生一次。此更改的結果是 目錄條目現在得到更新頻率更低,因此上次更新的文件大小比過去更新。

閱讀完整的文章是非常豐富和推薦!

5

我想你應該使用FileSystemWatcher並訂閱Changed事件。當指定的文件系統項目被改變時,它被觸發。

+0

+1一個合理的建議 - 在這一點上我有一個解決方法,並從長遠來看最有可能將重構使用FileSystemWatcher - 但這並不回答*爲什麼*我得到陳舊的信息,這是什麼這個問題全部關於 – BrokenGlass

+0

IMO必須由一些操作系統兌現層。即使你調用stream.Flush()它也不會強制保存HD。您是否嘗試禁用磁盤寫入緩存? http://support.microsoft.com/kb/259716 – Wojteq

+0

由於缺乏任何其他解釋,我將暫時接受此答案作爲解決方法。 – BrokenGlass

1

我同意Wojteq使用FileSystemWatcher類將是一個更好的解決方案。它暴露了文件或目錄的不同屬性發生變化時(例如他引用的Change事件)的事件,它比當前的輪詢解決方案更好。要回答您關於爲什麼Refresh需要不同時間來反映文件大小變化的問題,答案就是與Windows操作系統的底層虛擬內存管理器有關。當執行文件I/O時,它實際上會根據內存映射文件進行更新;這是由操作系統管理的文件的緩衝副本。所以,Windows控制何時將緩衝數據寫入磁盤。沒有辦法預測何時將特定的緩衝數據物理寫入磁盤。這意味着更新文件流會將這些更新放入緩衝區。如果您要Flush()流,緩衝更新應該立即寫入磁盤,如果關閉流,那麼在流關閉之後它將從緩衝區寫入磁盤,並且如果流保持打開狀態,到Windows時,它決定寫緩衝的數據到磁盤。

+0

緩衝將解釋以秒爲單位但不是幾分鐘的更新延遲,寫入代碼以C爲單位,並使用'fwrite',默認情況下使用的緩衝區大小不超過幾千字節AFAIK。 – BrokenGlass

+0

+1當然,它確實與正在寫入的文件有關,我注意到一旦寫入過程停止,我立即得到正確的更新。儘管如此,陳舊的信息問題很少發生,但這仍然引發了一個問題:爲什麼這首先發生? – BrokenGlass