2013-03-20 1 views
5

как избежать ошибки FileSystemWatcher в C#?Избегайте ошибки слишком много изменений сразу в каталоге

слишком много изменений сразу в каталоге

Я должен обнаружить все изменения на сетевом ресурсе. ВнутреннийBufferSize увеличен до 8192 * 128

+1

Я отредактировал ваше название. Пожалуйста, смотрите: «Если вопросы включают« теги »в их названиях?] (Http://meta.stackexchange.com/questions/19190/), где консенсус« нет, они не должны ». –

ответ

2

У меня есть опыт, который FileSystemWatcher не всегда является самой надежной вещью в использовании. Вы можете указать фильтр, чтобы сузить файлы, которые вы просматриваете (NotifyFilter), или увеличить размер буфера.

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

+0

Вторая попытка также сбой: для каждого NotifyFilter это собственный FSW. Мне нужно обнаружить все изменения, чтобы зафиксировать их в базе данных (путь и контрольная сумма). Опроса на сетевом ресурсе 2 Тб недостаточно - и да, я уже использую команды оболочки для доступа ко всем путям (намного быстрее, чем System.IO). – DerAbt

+0

См. Также http: // stackoverflow.com/questions/239988/filesystemwatcher-vs-polling-to-watch-for-file-changes, в котором говорится, что при использовании этого ресурса он не является надежным, а опрос будет лучшим вариантом. –

+0

Опрос по 2 ТБ не вариант ;-) Спасибо. – DerAbt

2

От MSDN;

Операционная система Windows уведомит ваш компонент файла изменяется в буфере, созданного FileSystemWatcher. Если в течение короткого времени будет много изменений , буфер может переполняться. Это приведет к тому, что компонент потеряет информацию об изменениях в каталоге, и он будет только предоставить уведомление об оплате. Увеличение размера буфера с InternalBufferSize является дорогостоящим, так как оно исходит из незагружаемой памяти, которая не может быть заменена на диск, поэтому держите буфер как маленький, но достаточно большой, чтобы не пропустить какие-либо события смены файла. Чтобы избежать переполнения буфера, используйте свойства NotifyFilter и IncludeSubdirectories, чтобы вы могли отфильтровать нежелательные изменения. .

+0

Я знаю. Я создал 40 файлов с помощью Copy'n'Paste и возникла ошибка. Всего 40 файлов. – DerAbt

+0

@DerAbt _Hmm_, похоже, что это не слишком много. Вы уверены, что ничего не сделали? –

+0

Да. Создал файл с помощью «Новый текстовый файл» и копировал его в 40 раз в одну папку. – DerAbt

0

SHChangeNotifyRegister может использоваться для получения уведомлений об оболочке.

0

Он должен быть исправлен, если увеличить размер буфера, но это не практическое решение. Потому что, чтобы убедиться, что он всегда записывает все, что вам нужно, чтобы сделать буфер огромным. И это будет влиять на производительность greatlly. И я думаю, что проблемы с производительностью можно устранить, реализовав многопоточность.

4

Возможно, я нашел шаблон, который может помочь значительно улучшить использование буфера.

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

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

Если вместо выполнения операций внутри делегатов вы просто ставите их в очередь и откладываете их выполнение для рабочего фона, объем используемой им памяти будет значительно меньше.

На самом деле, я понятия не имею, почему класс сам по себе не реализует что-то подобное в первую очередь.

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

using System; 
using System.Collections.Concurrent; 
using System.ComponentModel; 
using System.IO; 
using System.Threading; 
using NUnit.Framework; 

namespace Soundnet.Synchronisation.FileSystemWatcherTests 
{ 


    /// <summary> 
    /// 
    /// </summary> 
    [TestFixture] 
    public class Tests 
    { 

    static readonly ConcurrentQueue<Change> ChangesQueue = new ConcurrentQueue<Change>(); 
    const string Destination = @"c:\Destination"; 
    const string Source = @"c:\Source"; 

    /// <summary> 
    /// Tests this instance. 
    /// </summary> 
    [Test] 
    public void Test() 
    { 
     var changesBackgroundWorker = new BackgroundWorker(); 
     changesBackgroundWorker.DoWork += ChangesBackgroundWorkerOnDoWork; 
     changesBackgroundWorker.RunWorkerAsync(); 
     var fileSystemWatcher = new FileSystemWatcher 
           { 
            Path = Source, 
            EnableRaisingEvents = true, 
            IncludeSubdirectories = true, 
            NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess | NotifyFilters.CreationTime | NotifyFilters.FileName | NotifyFilters.DirectoryName, 
            InternalBufferSize = 65536 
           }; 
     fileSystemWatcher.Created += FileSystemWatcherOnCreated; 
     fileSystemWatcher.Deleted += FileSystemWatcherOnDeleted; 
     fileSystemWatcher.Renamed += FileSystemWatcherOnRenamed; 
     fileSystemWatcher.Error += FileSystemWatcherOnError; 

     while (true) 
     Thread.Sleep(1000000); 
    } 

    /// <summary> 
    /// Changeses the background worker configuration document work. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="doWorkEventArgs">The <see cref="DoWorkEventArgs"/> instance containing the event data.</param> 
    /// <exception cref="System.ArgumentOutOfRangeException"></exception> 
    private static void ChangesBackgroundWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs) 
    { 
     while (true) 
     { 
     Change change; 
     if (ChangesQueue.TryDequeue(out change)) 
     { 
      var backgroundWorker = new BackgroundWorker(); 
      switch (change.ChangeType) 
      { 
      case WatcherChangeTypes.Created: 
       backgroundWorker.DoWork += (o, args) => 
              { 
              var fileSystemType = GetFileSystemType(change.FullPath); 

              var newItem = Path.Combine(Destination, change.Name); 
              while (true) 
              { 
               try 
               { 
               switch (fileSystemType) 
               { 
                case FileSystemType.File: 
                File.Copy(change.FullPath, newItem, true); 
                break; 
                case FileSystemType.Directory: 
                var directorySecurity = 
                 Directory.GetAccessControl(change.FullPath); 
                Directory.CreateDirectory(newItem, directorySecurity); 
                break; 
                case FileSystemType.NotExistant: 
                break; 
               } 
               return; 
               } 
               catch (IOException exception) 
               { 
               Thread.Sleep(100); 
               Console.WriteLine(exception.Message); 
               } 
              } 
              }; 
       break; 
      case WatcherChangeTypes.Deleted: 
       backgroundWorker.DoWork += (o, args) => 
       { 
       var itemToDelete = Path.Combine(Destination, change.Name); 
       var fileSystemType = GetFileSystemType(itemToDelete); 
       switch (fileSystemType) 
       { 
        case FileSystemType.File: 
        File.Delete(itemToDelete); 
        break; 
        case FileSystemType.Directory: 
        Directory.Delete(itemToDelete, true); 
        break; 
       } 
       }; 
       break; 
      case WatcherChangeTypes.Changed: 
       backgroundWorker.DoWork += (o, args) => 
       { 
       var fileSystemType = GetFileSystemType(change.FullPath); 
       var newItem = Path.Combine(Destination, change.Name); 
       switch (fileSystemType) 
       { 
        case FileSystemType.File: 
        File.Copy(change.FullPath, newItem, true); 
        break; 
       } 
       }; 
       break; 
      case WatcherChangeTypes.Renamed: 
       backgroundWorker.DoWork += (o, args) => 
       { 
       var fileSystemType = GetFileSystemType(change.FullPath); 
       var oldItem = Path.Combine(Destination, change.OldName); 
       var newItem = Path.Combine(Destination, change.Name); 
       switch (fileSystemType) 
       { 
        case FileSystemType.File: 
        if (File.Exists(oldItem)) 
         File.Move(oldItem, newItem); 
        break; 
        case FileSystemType.Directory: 
        if (Directory.Exists(oldItem)) 
         Directory.Move(oldItem, newItem); 
        break; 
       } 
       }; 
       break; 
      case WatcherChangeTypes.All: 
       break; 
      default: 
       throw new ArgumentOutOfRangeException(); 
      } 
      backgroundWorker.RunWorkerAsync(); 
     } 
     } 
    } 

    /// <summary> 
    /// Files the system watcher configuration created. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnCreated(object sender, FileSystemEventArgs fileSystemEventArgs) 
    { 
     ChangesQueue.Enqueue(new Change 
     { 
     ChangeType = WatcherChangeTypes.Created, 
     FullPath = fileSystemEventArgs.FullPath, 
     Name = fileSystemEventArgs.Name 
     }); 
    } 

    /// <summary> 
    /// Files the system watcher configuration deleted. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="fileSystemEventArgs">The <see cref="FileSystemEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnDeleted(object sender, FileSystemEventArgs fileSystemEventArgs) 
    { 
     ChangesQueue.Enqueue(new Change 
     { 
     ChangeType = WatcherChangeTypes.Deleted, 
     FullPath = fileSystemEventArgs.FullPath, 
     Name = fileSystemEventArgs.Name 
     }); 
    } 

    /// <summary> 
    /// Files the system watcher configuration error. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="errorEventArgs">The <see cref="ErrorEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnError(object sender, ErrorEventArgs errorEventArgs) 
    { 
     var exception = errorEventArgs.GetException(); 
     Console.WriteLine(exception.Message); 
    } 

    /// <summary> 
    /// Files the system watcher configuration renamed. 
    /// </summary> 
    /// <param name="sender">The sender.</param> 
    /// <param name="fileSystemEventArgs">The <see cref="RenamedEventArgs"/> instance containing the event data.</param> 
    private static void FileSystemWatcherOnRenamed(object sender, RenamedEventArgs fileSystemEventArgs) 
    { 
     ChangesQueue.Enqueue(new Change 
     { 
     ChangeType = WatcherChangeTypes.Renamed, 
     FullPath = fileSystemEventArgs.FullPath, 
     Name = fileSystemEventArgs.Name, 
     OldFullPath = fileSystemEventArgs.OldFullPath, 
     OldName = fileSystemEventArgs.OldName 
     }); 
    } 

    /// <summary> 
    /// Gets the type of the file system. 
    /// </summary> 
    /// <param name="fullPath">The full path.</param> 
    /// <returns></returns> 
    private static FileSystemType GetFileSystemType(string fullPath) 
    { 
     if (Directory.Exists(fullPath)) 
     return FileSystemType.Directory; 
     if (File.Exists(fullPath)) 
     return FileSystemType.File; 
     return FileSystemType.NotExistant; 
    } 
    } 

    /// <summary> 
    /// Type of file system object 
    /// </summary> 
    internal enum FileSystemType 
    { 
    /// <summary> 
    /// The file 
    /// </summary> 
    File, 
    /// <summary> 
    /// The directory 
    /// </summary> 
    Directory, 
    /// <summary> 
    /// The not existant 
    /// </summary> 
    NotExistant 
    } 

    /// <summary> 
    /// Change information 
    /// </summary> 
    public class Change 
    { 
    /// <summary> 
    /// Gets or sets the type of the change. 
    /// </summary> 
    /// <value> 
    /// The type of the change. 
    /// </value> 
    public WatcherChangeTypes ChangeType { get; set; } 

    /// <summary> 
    /// Gets or sets the full path. 
    /// </summary> 
    /// <value> 
    /// The full path. 
    /// </value> 
    public string FullPath { get; set; } 

    /// <summary> 
    /// Gets or sets the name. 
    /// </summary> 
    /// <value> 
    /// The name. 
    /// </value> 
    public string Name { get; set; } 

    /// <summary> 
    /// Gets or sets the old full path. 
    /// </summary> 
    /// <value> 
    /// The old full path. 
    /// </value> 
    public string OldFullPath { get; set; } 

    /// <summary> 
    /// Gets or sets the old name. 
    /// </summary> 
    /// <value> 
    /// The old name. 
    /// </value> 
    public string OldName { get; set; } 
    } 
} 
5

Есть две вещи, которые вы должны сделать:

  1. Set InternalBufferSize до максимального поддерживаемого значения (65536). Ваша попытка установить его на «8192 * 128» больше, чем максимальное поддерживаемое значение, указанное в documentation, так что вы, возможно, не увеличили размер буфера вообще.
  2. События очереди от FileSystemWatcher на фоне потока для обработки.

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

Вот что я делаю в своем коде. Во-первых, я начинаю свою собственную диспетчерскую нить:

Dispatcher changeDispatcher = null; 
ManualResetEvent changeDispatcherStarted = new ManualResetEvent(false); 
Action changeThreadHandler =() => 
{ 
    changeDispatcher = Dispatcher.CurrentDispatcher; 
    changeDispatcherStarted.Set(); 
    Dispatcher.Run(); 
}; 
new Thread(() => changeThreadHandler()) { IsBackground = true }.Start(); 
changeDispatcherStarted.WaitOne(); 

Затем создаю наблюдателя. Обратите внимание на размер буфера. В моем случае, я только следить за изменения в целевом каталоге, а не подкаталоги:

FileSystemWatcher watcher = new FileSystemWatcher(); 
watcher.Path = path; 
watcher.InternalBufferSize = 64 * 1024; 
watcher.IncludeSubdirectories = false; 

Теперь я креплю свои обработчик событий, но здесь я вызываю их на мой диспетчер, а не запуская их синхронно в наблюдателе потоке. Да, события будут обрабатываться в порядке диспетчер:

watcher.Changed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnChanged(sender, e))); 
watcher.Created += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnCreated(sender, e))); 
watcher.Deleted += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnDeleted(sender, e))); 
watcher.Renamed += (sender, e) => changeDispatcher.BeginInvoke(new Action(() => OnRenamed(sender, e))); 

И, наконец, после утилизации FileSystemWatcher (? Вы делали это, право), вы должны выключить диспетчер:

watcher.Dispose() 
changeDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal); 

И все. Я сам получал эту проблему, как в сетевом, так и в локальном сценариях. После использования этого подхода я не смог снова сгенерировать эту ошибку, даже когда вы набирали пустые файлы для просмотра каталогов как можно быстрее. Если вам когда-либо удавалось каким-то образом исчерпать буфер в этом случае (что, я уверен, возможно, API вверх по течению, вероятно, медленнее), здесь еще есть место для оптимизации. Пока ваш диспетчер находится за «точкой опроса», хотя отправитель не может отправлять события быстрее, чем вы можете их отправить, вы никогда не получите отставание и, следовательно, никогда не будите буфера. Я считаю, что этот подход ставит вас в безопасную зону.

+0

И я только что заметил, что Луис в основном сказал то же самое более двух лет назад. Ну, вот пример альтернативного кода, и этот используется в производственных средах. –

Смежные вопросы