改名事件配不成對,因為順序從未寫進規格
The Old New Thing(Raymond Chen,Microsoft DevBlogs)· 2026-09-14
Windows 的目錄監控 API ReadDirectoryChangesW,偵測到一次改名(rename)動作時會送出兩則完全獨立的通知——FILE_ACTION_RENAMED_OLD_NAME 與 FILE_ACTION_RENAMED_NEW_NAME——但兩者之間沒有任何共用識別碼可以配對。這不是刻意的設計取捨,而是文件從一開始就沒有把「這兩則通知必定緊接著出現」這件事寫下來。
微軟資深工程師 Raymond Chen 在部落格 The Old New Thing 於 2026 年 9 月 14 日回覆讀者 Brian Dellisanti 的提問時給出這個說法,並坦承「我當時不在場,只能用猜的」。
背景
ReadDirectoryChangesW 是 Win32 API,用來監控指定目錄(可選是否含子目錄)的檔案異動,變更結果會以 FILE_NOTIFY_INFORMATION 緩衝區同步或非同步回傳給呼叫端。這是任何需要「目錄一有變化就被通知」的程式的底層機制——檔案同步工具、即時備份軟體、防毒監控、跨平台 file-watcher 函式庫,都是靠它才能不用不斷輪詢就知道使用者改了什麼檔案。
BOOL ReadDirectoryChangesW(
HANDLE hDirectory,
LPVOID lpBuffer,
DWORD nBufferLength,
BOOL bWatchSubtree,
DWORD dwNotifyFilter,
...
);問題出在改名這個動作的回報方式。一個檔案被改名時,程式收到的不是一則「rename」事件,而是兩則各自獨立的通知:先是帶著舊檔名的 FILE_ACTION_RENAMED_OLD_NAME,接著是帶著新檔名的 FILE_ACTION_RENAMED_NEW_NAME。這兩則通知除了都來自同一次系統呼叫填出的緩衝區之外,沒有任何欄位——沒有交易 ID、沒有序號——能讓你確定某則 NEW_NAME 對應的就是某則 OLD_NAME。對寫檔案同步工具的工程師來說,這代表只能靠「這兩則通知是不是緊接在一起收到」來猜配對;一旦目錄裡同時有多個檔案在改名,這個假設立刻站不住腳。
FILE_ACTION_RENAMED_OLD_NAME
FILE_ACTION_RENAMED_NEW_NAME背後原因
Chen 自己的猜測是(他明白表示不確定):最初的實作方式裡,兩則改名通知本來就是一前一後緊接著出現,呼叫端因此很自然學會「看到 OLD_NAME 先存起舊檔名,緊接著收到的 NEW_NAME 就是配對的另一半」這種寫法。問題是這個行為從沒被寫進正式文件,變成一個沒有白紙黑字保證的默契。
後來新的檔案系統加入 Windows 生態時,沒有任何規格要求它們遵守這個沒寫下來的順序。Chen 問:如果兩個檔案同時在改名,兩組通知會不會被交錯送出?「文件裡沒有寫著不准這樣做,所以我猜這是可能的。」他也坦承自己並不確定是否真的有檔案系統違反過這個默契——就他所知,各檔案系統目前仍是連續送出兩則通知,但「連續送出」不等於保證「緊接著送出」,尤其當磁碟同時有大量並行活動時。
他還觀察到一個更根本的現實:實務上真正去追蹤 rename 配對的程式碼並不多。大多數程式乾脆把 OLD_NAME 當成刪除、NEW_NAME 當成新增來處理,完全放棄配對;少數真的想追蹤 rename 的程式,則直接假設兩則通知不會交錯——「反正他們也沒有別的選擇」。
Chen 也提到後來的 ReadDirectoryChangesExW 搭配 ReadDirectoryNotifyExtendedInformation 回傳的額外資訊,剛好能用來把改名的兩側可靠地接起來,但他特別強調這從來不是這個延伸 API 當初設計的目的,只是「順帶的好處」(happy side effect)。
影響範圍
受影響的是任何用 ReadDirectoryChangesW 監控目錄變化來做即時同步、備份,或是寫跨平台 file-watcher 抽象層(需要把 Windows 原生事件轉換成統一 rename 事件)的工程師。這些工具如果要處理多檔案同時改名的場景——例如批次重新命名、IDE 專案整批搬移——目前依賴「兩則通知必定緊接出現」這個假設的程式碼,在高並行磁碟活動下並沒有文件保證其行為正確。
| ReadDirectoryChangesW | ReadDirectoryChangesExW | |
|---|---|---|
| 改名事件 | OLD_NAME / NEW_NAME 各自獨立 | 同左,但多帶額外資訊 |
| 配對依據 | 假設緊接出現(文件未保證) | 額外資訊可拿來配對(非原始設計目的) |
Chen 在文章裡指出的做法,是能升級的話改用 ReadDirectoryChangesExW,靠它回傳的額外資訊把改名兩側接起來——這是文章裡唯一提到、可用來可靠配對的方式。若受限於系統版本只能用原本的 ReadDirectoryChangesW,文章並未提供其他替代方案。