Обнаружение изменений файлов с помощью FileSystemWatcher C#


Если вы разрабатываете приложение в стиле проводника Windows или интегрируетесь с устаревшей или сторонней системой, которая может предоставлять информацию только через файлы, вам может потребоваться отслеживать файлы в папке. Класс FileSystemWatcher дает возможность сделать это.

Класс FileSystemWatcher

Класс FileSystemWatcher находится в пространстве имен System.IO. Это интересный класс, который предоставляет все функциональные возможности, необходимые для мониторинга каталога на диске и определения того, когда изменяется его содержимое. Это достигается за счет прямой связи с уведомлениями файловой системы, предоставляемыми операционной системой Windows, и создания событий при изменении элементов.

Класс FileSystemWatcher предоставляет четыре события, которые вызываются для указания изменения файла или папки. Четыре события и действия, которые они обозначают, являются:

  • Changed. Возникает при изменении файла или папки.
  • Created. Возникает при создании нового файла или папки.
  • Deleted. Возникает при удалении существующего файла или папки.
  • Renamed. Возникает при переименовании существующего файла или папки.
Создание проекта монитора файловой системы

В этой статье мы создадим новый проект Windows Forms, чтобы продемонстрировать использование класса FileSystemWatcher. Это будет простое приложение, которое отслеживает изменения файлов в папке. Всякий раз, когда вносится изменение, оно заносится в список в основной форме приложения.

Для начала создайте новый проект Windows Forms с именем "Filesystemmonitor".

Создание формы файлового монитора

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



Добавление наблюдателя файловой системы

Чтобы разрешить мониторинг изменений файлов, нам нужен объект FileSystemWatcher. Объект будет настроен с помощью событий формы при изменении пути к монитору и параметров фильтрации, а также при включении и отключении мониторинга. Чтобы разрешить такую конфигурацию, объект должен иметь переменную область видимости, которая видна всем методам формы. Поэтому он будет создан в блоке кода класса.

В системе появится наблюдатель за файловой системой.Пространство имен ввода-вывода, поэтому убедитесь, что у вас есть система использования.Ввод-вывод; в верхней части кода формы перед добавлением следующего объявления в класс FileMonitorForm:

private FileSystemWatcher _watcher;
Предотвращение операций с перекрестными потоками

По умолчанию, когда объект FileSystemWatcher вызывает события уведомлений, вызовы делегатов выполняются в потоке из пула системных потоков. Как правило, это будет не тот поток, который используется для управления формой. Поскольку демонстрационное приложение потребует, чтобы изменения файла регистрировались в визуальном элементе формы, использование выделенного потока для изменения содержимого списка приведет к операции с перекрестными потоками и возникновению исключения IllegalOperationException.

Чтобы заставить наблюдатель файловой системы использовать поток, в котором работает форма, и избежать ошибок, можно использовать свойство SynchronizingObject. Это свойство будет присвоено классу формы, чтобы объект формы использовался для маршалинга вызовов обработчика событий. Чтобы задать это свойство, измените конструктор формы следующим образом:

public FileMonitorForm()
{
    InitializeComponent();
 
    _watcher = new FileSystemWatcher();
    _watcher.SynchronizingObject = this;
}
Отслеживание событий изменения файла

Наблюдатель файловой системы вызывает четыре события в ответ на изменения в отслеживаемой папке. Они предоставляют информацию для создания, изменения, удаления и переименования файлов и папок. Первые три из них основаны на делегате FileSystemEventHandler. Этот делегат включает параметр FileSystemEventArgs, который предоставляет информацию, касающуюся пути и имени измененного файла или папки.

Переименованное событие основано на делегате RenamedEventHandler. Этот делегат предоставляет объект RenamedEventArgs с информацией, описывающей произошедшее действие. Класс RenamedEventArgs является производным от FileSystemEventArgs, но добавляет дополнительные свойства для хранения старого имени файла или папки.

Чтобы прикрепить к четырем событиям в примере приложения, добавьте следующие четыре строки в конец конструктора:

_watcher.Changed += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Created += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Deleted += new FileSystemEventHandler(LogFileSystemChanges);
_watcher.Renamed += new RenamedEventHandler(LogFileSystemRenaming);
Теперь нам нужно создать два метода для обработки уведомлений. Первый метод будет обрабатывать Changed, Created и Deleted события. В каждом случае информация из аргументов события будет записана в поле списка. В списке будет отображаться время изменения, имя затронутого файла и тип произошедшего изменения.

Добавьте следующий метод для регистрации изменений. Текст лога создается с помощью метода String.Format с заполнителем {0:G}, указывающим, что текущая дата и время будут отформатированы с использованием предпочитаемого пользователем формата короткой даты и формата длительного времени. Свойство FullPath возвращает полный путь к измененному файлу. Свойство ChangeType возвращает значение из перечисления WatcherChangeTypes, которое автоматически преобразуется в читаемую строку методом Format.

void LogFileSystemChanges(object sender, FileSystemEventArgs e)
{
    string log = string.Format("{0:G} | {1} | {2}", DateTime.Now, e.FullPath, e.ChangeType);
    ChangeLogList.Items.Add(log);
}
Второй способ добавления будет обрабатывать событие, возникающее при переименовании файла или папки. Этот метод аналогичен предыдущему, за исключением того, что тип изменения всегда "переименован". Мы запишем старое имя и новый путь и имя для измененного элемента.

Добавьте следующий метод для переименования лога:

void LogFileSystemRenaming(object sender, RenamedEventArgs e)
{
    string log = string.Format("{0:G} | {1} | Renamed from {2}", DateTime.Now
        , e.FullPath, e.OldName);
    ChangeLogList.Items.Add(log);
}
Включение мониторинга

При создании объекта FileSystemWatcher возможности создания событий отключаются. Чтобы включить события, свойству EnableRaisingEvents присвоено значение true. Сброс значения свойства на false в любое время останавливает возникновение событий изменения файла. Однако, прежде чем события будут включены, путь к каталогу для мониторинга должен быть задан в свойстве Path.

В демонстрационной программе мы свяжем свойство EnableRaisingEvents с флажком "Мониторинг ввода". Затем можно будет включать и выключать события по мере необходимости. Всякий раз, когда включено создание событий, также будет задан путь для мониторинга. В то же время мы включим или отключим текстовые поля, чтобы изменения можно было вносить только тогда, когда события отключены. Хотя это не является обязательным требованием, это упростит разработку примерной программы.

Чтобы включить функцию флажка, добавьте следующий метод и свяжите его с событием CheckChanged флажка:

private void MonitoringInput_CheckedChanged(object sender, EventArgs e)
{
    _watcher.Path = FolderInput.Text;
    _watcher.EnableRaisingEvents = MonitoringInput.Checked;
 
    FolderInput.Enabled = FileFilterInput.Enabled = !MonitoringInput.Checked;
}
Примечание: Обычно вы включаете проверку, чтобы убедиться, что отслеживаемая папка существует. Чтобы упростить пример для этой статьи, код проверки не отображается.

Тестирование программы

Теперь вы можете протестировать программу. Как только запущенная форма станет видимой, измените содержимое текстового поля, содержащего путь для мониторинга, на каталог, существующий на вашем жестком диске. Установите флажок MonitoringInput, чтобы включить функцию наблюдения за файловой системой, и попробуйте создавать, изменять, удалять и переименовывать файлы и папки в этом каталоге, чтобы увидеть регистрируемые события.

Вы можете заметить, что некоторые изменения в файлах вызывают более одного события, в зависимости от приложения, используемого для редактирования файлов. Это важно отметить, так как в некоторых обстоятельствах вы не захотите обрабатывать дублированные события. Обратите также внимание, что изменения содержимого папки в отслеживаемом каталоге приводят к возникновению измененных событий. На данный момент, однако, изменения во вложенных папках на более глубоком уровне не вызывают событий.

Размер буфера и событие ошибки

Класс FileSystemWatcher работает, записывая все соответствующие файлы и более ранние изменения и помещая их в буфер. Затем это обрабатывается по одному изменению за раз, пока все уведомления не будут обработаны и буфер не опустеет. По умолчанию внутренний буфер имеет размер восемь килобайт (8192 байта). Каждое событие может занимать до шестнадцати байт буфера для своих данных, не включая имя файла. Это означает, что при большом количестве изменений за короткий промежуток времени буфер может быстро перегружаться и уведомления могут быть потеряны.

Одним из способов решения проблемы с размером буфера является изменение максимально допустимого размера. Если вы знаете, что ваша программа, скорее всего, обработает большое количество изменений файлов за короткий период, вы можете изменить значение в свойстве InternalBufferSize. Это свойство позволяет указать размер буфера в виде количества байтов. Следует соблюдать осторожность, так как буфер хранится в памяти, которую нельзя перенести на диск, поэтому эта память становится недоступной для других программ. Выбранный размер должен быть кратен 4096, чтобы обеспечить наилучшую производительность.

Каждый раз, когда достигается предельный размер буфера, объект FileSystemWatcher вызывает событие ошибки. Это событие позволяет определить, что произошло изменение, но сведения об измененном файле или папке будут недоступны. Как правило, вы регистрируете это или создаете какое-либо уведомление, чтобы можно было попытаться восстановить.

Чтобы зафиксировать событие ошибки в примере программы, добавьте следующую строку в конец конструктора формы:

_watcher.Error += new ErrorEventHandler(LogBufferError);
Чтобы обработать это событие, добавьте следующий метод в класс формы:

void LogBufferError(object sender, ErrorEventArgs e)
{
    string log = string.Format("{0:G} | Buffer limit exceeded", DateTime.Now);
    ChangeLogList.Items.Add(log);
}
Вы можете проверить это событие, либо уменьшив размер буфера объекта FileSystemWatcher, либо обработав большое количество файлов в отслеживаемой папке. (Создание копий тысяч файлов может быть использовано для перегрузки буфера.)

Фильтрация отслеживаемых файлов

При мониторинге папки на предмет изменений в файле определенного типа или в файлах с определенными именами к объекту FileSystemWatcher может быть применен фильтр имен. После применения события возникают только в том случае, если имя измененного файла или папки соответствует условию фильтра. Это имеет потенциальное преимущество в снижении использования буфера.

Чтобы применить фильтр, задается свойство фильтра. Если необходимо отслеживать только один файл, указывается имя файла. Когда требуется ограниченный набор файлов или папок, в строку фильтра могут быть включены подстановочные знаки. Символ звездочки (*) используется для обозначения любого количества символов, а знак вопроса (?) используется для замены любого отдельного символа. Некоторые примеры строк фильтра показаны в таблице ниже.



Чтобы применить фильтрацию имен файлов в демонстрационном приложении, мы свяжем содержимое текстового поля FileFilterInput со свойством Фильтра FileSystemWatcher. Это будет сделано путем добавления строки кода в метод, который обнаруживает изменения в статусе флажка. Измените метод следующим образом:

private void MonitoringInput_CheckedChanged(object sender, EventArgs e)
{
    _watcher.Path = FolderInput.Text;
    _watcher.Filter = FileFilterInput.Text;
    _watcher.EnableRaisingEvents = MonitoringInput.Checked;
 
    FolderInput.Enabled = FileFilterInput.Enabled = !MonitoringInput.Checked;
}
После добавления кода фильтрации протестируйте приложение с использованием различных фильтров.

Фильтрация отслеживаемых действий

Второй тип фильтрации может быть применен для ограничения типов изменений, вызывающих возникновение события уведомления. Эта фильтрация применяется путем установки свойства NotifyFilter для FileSystemWatcher. Свойство принимает значение, основанное на перечислении NotifyFilters. Это перечисление имеет атрибут FlagsAttribute, поэтому различные значения могут быть объединены с помощью побитовых логических операторов. Это позволяет включить несколько типов уведомлений, игнорируя другие. использование этого типа фильтрации уменьшает количество уведомлений, отправляемых во внутренний буфер, и связанный с этим риск заполнения буфера.

В перечислении определены следующие постоянные значения и связанные с ними типы уведомлений:



Мониторинг вложенных папок

Последнее свойство, которое следует учитывать, - это IncludeSubdirectories. Это логическое значение используется для указания того, следует ли отслеживать подпапки отслеживаемого каталога. Если установлено значение true, наблюдается вся иерархия папок ниже указанного пути, и любые изменения приводят к возникновению событий. Этот параметр следует использовать с осторожностью, так как он может привести к неожиданно большому количеству уведомлений, добавляемых в буфер, увеличивая риск того, что он будет переполнен.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, си шарп, файл




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Бесплатные уроки по Java
Урок 5. Конкатенация строк JavaScript
Урок 17. Наследование и полиморфизм в Java