星期日, 1月 14, 2024

[.NET] FileSystemWatcher

在 [C#] 檔案系統監視 筆記發現當初寫的還蠻簡易的,該篇從官方文件重新了解 FileSytemWatcher,這次主要參考這兩篇官方文章內範例
FileSystemWatcher 在 .NET Framework 和 .NET 基本上是通用,Core 3.0 版本時增加FileSystemWatcher.Filters 屬性而已

C# Code
using System.Text;

namespace FileSystemWatcherSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            using var watcher = new FileSystemWatcher();

            // 預設值為空值
            watcher.Path = @"D:\FileSystemWatcherDir";

            // 預設值為 NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName
            watcher.NotifyFilter = NotifyFilters.Attributes
                                 | NotifyFilters.CreationTime
                                 | NotifyFilters.DirectoryName
                                 | NotifyFilters.FileName
                                 | NotifyFilters.LastAccess
                                 | NotifyFilters.LastWrite
                                 | NotifyFilters.Security
                                 | NotifyFilters.Size;

            // 預設值為 false
            watcher.EnableRaisingEvents = true;

            // 預設值為 "*.*" (監看全部檔案)
            watcher.Filter = "*.txt";

            // 預設值為 false
            watcher.IncludeSubdirectories = true;

            // 預設值為 8,192 (8k)
            watcher.InternalBufferSize = 8192;

            // Changed、Created 和 Deleted 皆為 FileSystemEventArgs,可以共用 
            watcher.Changed += OnChanged;
            watcher.Created += OnChanged;
            watcher.Deleted += OnChanged;

            // Renamed 為 RenamedEventArgs
            watcher.Renamed += OnRenamed;

            // Error 為 ErrorEventArgs
            watcher.Error += OnError;

            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }

        private static void OnChanged(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine($"{e.ChangeType}: {e.FullPath}");
        }

        private static void OnRenamed(object sender, RenamedEventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine($"{e.ChangeType}:");
            sb.AppendLine($"    Old: {e.OldFullPath}");
            sb.AppendLine($"    New: {e.FullPath}");
            Console.WriteLine(sb.ToString());
        }

        private static void OnError(object sender, ErrorEventArgs e)
        {
            PrintException(e.GetException());
        }

        private static void PrintException(Exception? ex)
        {
            if (ex == null)
                return;

            StringBuilder sb = new StringBuilder();

            //  This can happen if Windows is reporting many file system events quickly
            //  and internal buffer of the  FileSystemWatcher is not large enough to handle this
            //  rate of events. The InternalBufferOverflowException error informs the application
            //  that some of the file system events are being lost.
            if (ex.GetType() == typeof(InternalBufferOverflowException))
                sb.AppendLine("Error 事件觸發,Internal Buffer Overflow 發生");
            else
                sb.AppendLine("Error 事件觸發,錯誤訊息如下");

            sb.AppendLine(ex.ToString());
            Console.WriteLine(sb.ToString());

            PrintException(ex.InnerException);
        }
    }
}

Path Property

指定監看的資料夾,該資料夾即使更名,FileSystemWatcher 仍然會繼續監看,FileSystemWatcher 是認 handle 來監看資料夾,而非資料夾名稱,詳見該段官方說明
When a directory is renamed, the FileSystemWatcher automatically reattaches itself to the newly renamed item. For example, if you set the Path property to "C:\My Documents" and then manually rename the directory to "C:\Your Documents", the component continues listening for change notifications on the newly renamed directory. However, when you ask for the Path property, it contains the old path. This happens because the component determines what directory watches based on the handle, rather than the name of the directory. Renaming does not affect the handle. So, if you destroy the component, and then recreate it without updating the Path property, your application will fail because the directory no longer exists.
如想監看特定檔案,請搭配  Filter Property 來達到

EnableRaisingEvents Property

在 VS2005 該預設值為 true,之後的 VS 版本預設為 false,啟用該設定並設定 Path Property,FileSystemWatcher 才會開始監看資料夾內的檔案變化

InternalBufferSize Property

預設值為 8,192 (8k),官方建議是設定在 4,096 - 65,536 (4k - 64k),要避免 overflow 發生,最好是明確定義下述三個屬性
  • IncludeSubdirectories:預設值為 false,不監看子目錄
  • NotifyFilter:預設值為 LastWrite | FileName | DirectoryName
  • Filter:預設為 *.* 監看全部檔案,最好能指定特定檔案或是檔案類型,EX:txt 檔案 
下述為官方詳細說明
You can set the buffer to 4 KB or larger, but it must not exceed 64 KB. If you try to set the InternalBufferSize property to less than 4096 bytes, your value is discarded and the InternalBufferSize property is set to 4096 bytes. For best performance, use a multiple of 4 KB on Intel-based computers. 

The system notifies the component of file changes, and it stores those changes in a buffer the component creates and passes to the APIs. Each event can use up to 16 bytes of memory, not including the file name. If there are many changes in a short time, the buffer can overflow. This causes the component to lose track of changes in the directory, and it will only provide blanket notification. Increasing the size of the buffer can prevent missing file system change events. However, increasing buffer size is expensive, because it comes from non-paged memory that cannot be swapped out to disk, so keep the buffer as small as possible. To avoid a buffer overflow, use the NotifyFilter and IncludeSubdirectories properties to filter out unwanted change notifications.

Error 事件

印象中使用 FileSystemWatcher 最好使用 try catch 來處理 Exception 發生的建議,但發現 FileSystemWatcher 本身就有 Error Event 可以使用,官方說明有提到當無法繼續執行監看檔案變更或在內部緩衝區溢位 (Internal Buffer Overflows) 時就會被觸發,不知道哪來的印象

Change 事件

監看的檔案或資料夾,發生下述情況變化會觸發 Change Event
  • 系統屬性 (System Attributes)
  • 上次寫入時間 (Last Write Time)
  • 上次存取時間 ( Last Access Time)
  • 安全性許可權 (Security Permissions)
  • 檔案或資料夾的大小 (Size)
檔案操作常常會伴隨多個事件被觸發,以檔案從資料夾搬移至另一個資料夾為例討論,可能會有多次的 Change Event、Created Event 、Deleted Event 觸發

Renamed 事件

FileSystemWatecher.Path 設定的監看資料夾更名並不會觸發 Renamed Event,監看資料夾內的檔案更名才會觸發

五大事件 EventArgs 整理
  • FileSystemEventArgs:Changed Event、Created Event、Deleted Event
  • RenamedEventArgs:Renamed Event
  • ErrorEventArgs:Error Event

執行觀察監看結果

觸發多個事件觀察:把檔案直接複製進資料夾
監看資料夾更名仍然可以運作:在資料夾內新增檔案後並更名,並更改監看資料夾名稱 (橘線代表),最後修改另一檔案內容,FileSystemWatcher 仍然有回應
觸發 Error Event:直接把資料夾剪下貼到其他地方去,故意放一個檔案在資料夾內,可以看見有 Changed 和 Deleted Event 被觸發,之後 Error Event 觸發並回報錯誤訊息

沒有留言:

張貼留言