FileSystemWatcher Измененное событие возникает дважды


336

У меня есть приложение, в котором я ищу текстовый файл, и если какие-либо изменения были внесены в файл, я использую OnChangedобработчик событий для обработки события. Я использую, NotifyFilters.LastWriteTimeно все равно событие запускается дважды. Вот код

public void Initialize()
{
   FileSystemWatcher _fileWatcher = new FileSystemWatcher();
  _fileWatcher.Path = "C:\\Folder";
  _fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
  _fileWatcher.Filter = "Version.txt";
  _fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
  _fileWatcher.EnableRaisingEvents = true;
}

private void OnChanged(object source, FileSystemEventArgs e)
{
   .......
}

В моем случае OnChangedвызывается дважды, когда я изменить текстовый файл version.txtи сохранить его.


2
@BrettRigby: Не удивительно. Ни один из этих возможных ответов не дает решения проблемы. Они все обходные пути для конкретных проблем. Фактически, ни один из них не решил мою конкретную проблему (должен признать, я не проверял их все).

Это обходной путь, но его следует оценивать по качеству обходного пути. Отслеживание изменений работает отлично, и это просто. OP запрашивает способ подавления повторяющихся событий, и вот что дают ответы ниже. msdn.microsoft.com/en-us/library/… Объясняет, что множественные события могут быть вызваны антивирусом или другими «сложными вещами файловой системы» (что звучит просто как оправдание).
Тайлер Монтни

2
Я недавно открыл эту проблему github.com/Microsoft/dotnet/issues/347
Стефан Альф

2
Я создал класс, который поможет вам получить только одно событие. Вы можете получить код от github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Ответы:


277

Я боюсь, что это хорошо известная ошибка / особенность FileSystemWatcherкласса. Это из документации класса:

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

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

Некоторое время назад я отметил страницу с несколькими советами FileSystemWatcher . Вы можете проверить это.



151

Я «исправил» эту проблему, используя следующую стратегию в моем делегате:

// fsw_ is the FileSystemWatcher instance used by my application.

private void OnDirectoryChanged(...)
{
   try
   {
      fsw_.EnableRaisingEvents = false;

      /* do my stuff once asynchronously */
   }

   finally
   {
      fsw_.EnableRaisingEvents = true;
   }
}

14
Я попробовал это, и это сработало, если я изменил один файл за раз, но если я изменил два файла за один раз (например, скопировать 1.txt и 2.txt, чтобы скопировать 1.txt и копию 2.txt), это вызовет только одно событие, а не два, как ожидалось.
Кристофер Пэйнтер

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

15
Это кажется, чтобы решить проблему, но это не так. Если другой процесс вносит изменения, вы можете их потерять. Причина, по которой он работает, заключается в том, что IO другого процесса асинхронен, и вы отключаете мониторинг до тех пор, пока не закончите свою обработку, создавая тем самым состояние гонки с другими событиями, которые могут представляет интерес. Вот почему @ChristopherPainter заметил его проблему.
Jf Beaulac

14
-1: Что делать, если другое изменение, которое вас заинтересует, произойдет при отключении?
Г. Стойнев

2
@cYounes: если вы не делаете свои вещи асинхронно.
Дэвид Брабант

107

Любые дублированные OnChangedсобытия из FileSystemWatcherмогут быть обнаружены и отброшены путем проверки File.GetLastWriteTimeметки времени в рассматриваемом файле. Вот так:

DateTime lastRead = DateTime.MinValue;

void OnChanged(object source, FileSystemEventArgs a)
{
    DateTime lastWriteTime = File.GetLastWriteTime(uri);
    if (lastWriteTime != lastRead)
    {
        doStuff();
        lastRead = lastWriteTime;
    }
    // else discard the (duplicated) OnChanged event
}

13
Мне нравится это решение, но я использовал Rx, чтобы сделать «правильную» вещь (смените "Rename"название интересующего вас события):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski

4
Я что-то упускаю? Я не понимаю, как это будет работать. Из того, что я видел, события запускаются одновременно, поэтому, если они оба одновременно войдут в указанное выше событие, они начнут выполняться до того, как будет установлен lastRead.
Питер Джамменсон

Как DateTimeтолько имеет разрешение в миллисекундах, этот метод работает, даже если вы замените File.GetLastWriteTimeна DateTime.Now. В зависимости от вашей ситуации вы также можете использовать a.FullNameглобальную переменную in для обнаружения повторяющихся событий.
Роланд

@PeterJamsmenson События не происходят точно одновременно. Например, Блокнот может генерировать несколько событий при сохранении изменений на диске, но эти события запускаются последовательно, один за другим, во время нескольких шагов, которые Блокнот должен выполнить для сохранения. Бабу метод отлично работает.
Роланд

10
Не работает, так как события срабатывают на расстоянии друг от друга: Время последней записи: 636076274162565607 Время последней записи: 636076274162655722
Аше

23

Вот мое решение, которое помогло мне остановить событие дважды:

watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;

Здесь я установил NotifyFilterсвойство только с именем файла и размером.
watcherмой объект FileSystemWatcher Надеюсь, это поможет.


9
Также в Блокноте я создал файл из четырех символов: abcd. Затем я открыл новый экземпляр Блокнота и ввел те же четыре символа. Я выбрал Файл | Сохранить как и выбрать тот же файл. Файл идентичен, а размер и имя файла не меняются, поскольку файл имеет четыре одинаковые буквы, поэтому он не запускается.
Rhyous

30
Вполне возможно, что может быть сделано подлинное изменение, которое не изменит размер файла, поэтому этот метод потерпит неудачу в такой ситуации.
Ли Гриссом

3
Я предполагаю, что это довольно распространенный случай, когда вы знаете, что любое значимое изменение изменит размер файла (например, мой случай был добавлением в файл журнала). Хотя любой, кто использует это решение, должен знать (и документировать) это предположение, это было именно то, что мне было нужно.
GrandOpener

1
@GrandOpener: это не всегда так. В моем случае я смотрю файлы, содержимое которых состоит только из одного символа, который либо 0, либо 1.

8

Мой сценарий состоит в том, что у меня есть виртуальная машина с сервером Linux. Я занимаюсь разработкой файлов на хосте Windows. Когда я изменяю что-то в папке на хосте, я хочу, чтобы все изменения были загружены, синхронизированы с виртуальным сервером через Ftp. Вот как я устраняю событие изменения дубликата при записи в файл (который также помечает папку, содержащую файл, который нужно изменить):

private Hashtable fileWriteTime = new Hashtable();

private void fsw_sync_Changed(object source, FileSystemEventArgs e)
{
    string path = e.FullPath.ToString();
    string currentLastWriteTime = File.GetLastWriteTime( e.FullPath ).ToString();

    // if there is no path info stored yet
    // or stored path has different time of write then the one now is inspected
    if ( !fileWriteTime.ContainsKey(path) ||
         fileWriteTime[path].ToString() != currentLastWriteTime
    )
    {
        //then we do the main thing
        log( "A CHANGE has occured with " + path );

        //lastly we update the last write time in the hashtable
        fileWriteTime[path] = currentLastWriteTime;
    }
}

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


Я предполагаю, что вы периодически очищаете хеш-таблицу.
ThunderGr

Это будет с точностью до секунды, но если период между двумя изменениями будет достаточно длинным, чтобы пройти секунду, он потерпит неудачу. Более того, если вы хотите больше точности, вы можете использовать, ToString("o")но будьте готовы к большему количеству ошибок
Pragmateek

5
Не сравнивайте строки, используйте DateTime.Equals ()
Филипп Камикадзе

Нет не надо Они не равны. В случае моего текущего проекта они находятся на расстоянии около миллисекунды. Я использую (newtime-oldtime) .TotalMilliseconds <(произвольный порог, обычно 5 мс).
Flynn1179

8

Попробуйте с этим кодом:

class WatchPlotDirectory
{
    bool let = false;
    FileSystemWatcher watcher;
    string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";

    public WatchPlotDirectory()
    {
        watcher = new FileSystemWatcher();
        watcher.Path = path;
        watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
                               | NotifyFilters.FileName | NotifyFilters.DirectoryName;
        watcher.Filter = "*.*";
        watcher.Changed += new FileSystemEventHandler(OnChanged);
        watcher.Renamed += new RenamedEventHandler(OnRenamed);
        watcher.EnableRaisingEvents = true;
    }



    void OnChanged(object sender, FileSystemEventArgs e)
    {
        if (let==false) {
            string mgs = string.Format("File {0} | {1}",
                                       e.FullPath, e.ChangeType);
            Console.WriteLine("onchange: " + mgs);
            let = true;
        }

        else
        {
            let = false;
        }


    }

    void OnRenamed(object sender, RenamedEventArgs e)
    {
        string log = string.Format("{0} | Renamed from {1}",
                                   e.FullPath, e.OldName);
        Console.WriteLine("onrenamed: " + log);

    }

    public void setPath(string path)
    {
        this.path = path;
    }
}

1
Это лучшее решение, использующее семафор вместо таймера.
Аарон Бленкуш

1
Какой семафор? Я вижу только логическую переменную здесь. Кроме того, основная проблема не решена: FileSystemEventHandler по-прежнему запускает несколько событий. И какой эффективности обладает этот код? if (let==false) { ... } else { let = false; }? Невероятно, как это получило голоса, это должно быть только из значков StackOverflow.
sɐunıɔ ןɐ qɐp

8

Вот мой подход:

// Consider having a List<String> named _changedFiles

private void OnChanged(object source, FileSystemEventArgs e)
{
    lock (_changedFiles)
    {
        if (_changedFiles.Contains(e.FullPath))
        {
            return;
        }
        _changedFiles.Add(e.FullPath);
    }

    // do your stuff

    System.Timers.Timer timer = new Timer(1000) { AutoReset = false };
    timer.Elapsed += (timerElapsedSender, timerElapsedArgs) =>
    {
        lock (_changedFiles)
        {
            _changedFiles.Remove(e.FullPath);
        }
    };
   timer.Start();
}

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

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


Ваше решение отлично работает для меня. Только вы забыли добавить файл в список _changedFiles. Первая часть кода должна выглядеть так:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey

Я опустил 4 ответа выше и проголосовал за этот. Ваш ответ - первый, который делает то, что должен делать, принимая событие LAST, а не первый. Как объясняет @Jorn, проблема в том, что файлы записываются в пакетном режиме. Другие решения не работали для меня.
CodingYourLife

Ваше решение не является потокобезопасным. _changedFilesДоступ из нескольких потоков. Один из способов исправить это - использовать ConcurrentDictionaryвместо List. Другой способ заключается в назначении тока Formна Timer.SynchronizingObjectимущество, а также в FileSystemWatcher.SynchronizingObjectсобственности.
Теодор Зулиас

5

Я создал репозиторий Git с классом, который расширяет FileSystemWatcherвозможности запуска событий только после завершения копирования. Он отменяет все измененные события, кроме последнего, и вызывает его только тогда, когда файл становится доступным для чтения.

Загрузите FileSystemSafeWatcher и добавьте его в свой проект.

Затем используйте его как нормальный FileSystemWatcherи наблюдайте, когда события вызваны.

var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;

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

Несмотря на опечатку в примере, это, кажется, жизнеспособное решение для меня. Тем не менее, в моем случае может произойти дюжина обновлений в течение одной секунды, поэтому мне пришлось резко снизить _consolidationInterval, чтобы не пропустить какие-либо изменения. Хотя кажется, что 10 мс - это нормально, я теряю около 50% обновлений, если установить для _consolidationInterval значение 50 мс. Мне все еще нужно выполнить несколько тестов, чтобы найти значение, которое подходит лучше всего.

_consolidationInterval, кажется, работает хорошо для меня. Я бы хотел, чтобы кто-то раскошелился и сделал пакет NuGet.
zumalifeguard

1
Спасибо :) Это решило мою проблему. Надеюсь, что созданные и скопированные события будут работать правильно с одним наблюдателем, чтобы решить эту проблему хорошо. stackoverflow.com/questions/55015132/…
техно,

1
Это отлично. Я внедрил это в свой проект, и он побеждал все попытки, которые я пытался сломать. Спасибо.
Христос

4

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

    Dictionary<string, DateTime> dateTimeDictionary = new Dictionary<string, DateTime>(); 

        private void OnChanged(object source, FileSystemEventArgs e)
            {
                if (!dateTimeDictionary.ContainsKey(e.FullPath) || (dateTimeDictionary.ContainsKey(e.FullPath) && System.IO.File.GetLastWriteTime(e.FullPath) != dateTimeDictionary[e.FullPath]))
                {
                    dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);

                    //your code here
                }
            }

Это надежное решение, но в нем отсутствует строка кода. в your code hereразделе вы должны добавить или обновить dateTimeDictionary. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake

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

3

Одним из возможных «взломов» может быть регулирование событий с помощью Reactive Extensions, например:

var watcher = new FileSystemWatcher("./");

Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
            .Throttle(new TimeSpan(500000))
            .Subscribe(HandleChangeEvent);

watcher.EnableRaisingEvents = true;

В этом случае я сокращаю до 50 мс, в моей системе этого было достаточно, но более высокие значения должны быть безопаснее. (И, как я уже сказал, это все еще «взлом»).


Я использовал, .Distinct(e => e.FullPath)что я считаю более интуитивным, чтобы иметь дело с. И вы восстановили поведение, которое можно ожидать от API.
Kjellski

3

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

private int fireCount = 0;
private void inputFileWatcher_Changed(object sender, FileSystemEventArgs e)
    {
       fireCount++;
       if (fireCount == 1)
        {
            MessageBox.Show("Fired only once!!");
            dowork();
        }
        else
        {
            fireCount = 0;
        }
    }
}

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

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

хотя не проверял это, я не совсем уверен, что это не работает для создания и удаления. это также должно быть теоретически применимо. Так как операторы fireCount ++ и if () являются атомарными и не будут отложены. даже с двумя вызванными событиями, конкурирующими друг с другом. Я предполагаю, что должно быть что-то еще, что вызывает твои проблемы. (потерян? Что ты имеешь в виду?)
Xiaoyuvax

3

Вот новое решение, которое вы можете попробовать. Хорошо работает для меня. В обработчике события для измененного события программно удалите обработчик из конструктора, при необходимости выведите сообщение, затем программно добавьте обработчик обратно. пример:

public void fileSystemWatcher1_Changed( object sender, System.IO.FileSystemEventArgs e )
    {            
        fileSystemWatcher1.Changed -= new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
        MessageBox.Show( "File has been uploaded to destination", "Success!" );
        fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler( fileSystemWatcher1_Changed );
    }

1
Вам не нужно вызывать конструктор типа делегата. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;должен делать правильные вещи.
bartonjs

@bartonjs Спасибо за это. Я не уверен, почему я вызвал весь конструктор. Честно говоря, это, скорее всего, ошибка новичка. Несмотря на это, похоже, что мой взлом исправления работал довольно хорошо.
Fancy_Mammoth

2

Основной причиной было то, что время последнего обращения к первому событию было текущее время (время записи или изменения файла). затем второе событие было исходным временем последнего доступа к файлу. Я решаю по коду.

        var lastRead = DateTime.MinValue;

        Watcher = new FileSystemWatcher(...)
        {
            NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
            Filter = "*.dll",
            IncludeSubdirectories = false,
        };
        Watcher.Changed += (senderObject, ea) =>
        {
            var now = DateTime.Now;
            var lastWriteTime = File.GetLastWriteTime(ea.FullPath);

            if (now == lastWriteTime)
            {
                return;
            }

            if (lastWriteTime != lastRead)
            {
                // do something...
                lastRead = lastWriteTime;
            }
        };

        Watcher.EnableRaisingEvents = true;

так же, как этот ответ
Жан-Поль

2

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

private void EventCallback(object sender, FileSystemEventArgs e)
{
    var fileName = e.FullPath;

    if (!File.Exists(fileName))
    {
        // We've dealt with the file, this is just supressing further events.
        return;
    }

    // File exists, so move it to a working directory. 
    File.Move(fileName, [working directory]);

    // Kick-off whatever processing is required.
}

2

Этот код работал для меня.

        private void OnChanged(object source, FileSystemEventArgs e)
    {

        string fullFilePath = e.FullPath.ToString();
        string fullURL = buildTheUrlFromStudyXML(fullFilePath);

        System.Diagnostics.Process.Start("iexplore", fullURL);

        Timer timer = new Timer();
        ((FileSystemWatcher)source).Changed -= new FileSystemEventHandler(OnChanged);
        timer.Interval = 1000;
        timer.Elapsed += new ElapsedEventHandler(t_Elapsed);
        timer.Start();
    }

    private void t_Elapsed(object sender, ElapsedEventArgs e)
    {
        ((Timer)sender).Stop();
        theWatcher.Changed += new FileSystemEventHandler(OnChanged);
    }

2

в основном на будущее мне :)

Я написал обертку, используя Rx:

 public class WatcherWrapper : IDisposable
{
    private readonly FileSystemWatcher _fileWatcher;
    private readonly Subject<FileSystemEventArgs> _infoSubject;
    private Subject<FileSystemEventArgs> _eventSubject;

    public WatcherWrapper(string path, string nameFilter = "*.*", NotifyFilters? notifyFilters = null)
    {
        _fileWatcher = new FileSystemWatcher(path, nameFilter);

        if (notifyFilters != null)
        {
            _fileWatcher.NotifyFilter = notifyFilters.Value;
        }

        _infoSubject = new Subject<FileSystemEventArgs>();
        _eventSubject = new Subject<FileSystemEventArgs>();

        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Changed").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);
        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Created").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);
        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Deleted").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);
        Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Renamed").Select(e => e.EventArgs)
            .Subscribe(_infoSubject.OnNext);

        // this takes care of double events and still works with changing the name of the same file after a while
        _infoSubject.Buffer(TimeSpan.FromMilliseconds(20))
            .Select(x => x.GroupBy(z => z.FullPath).Select(z => z.LastOrDefault()).Subscribe(
                infos =>
                {
                    if (infos != null)
                        foreach (var info in infos)
                        {
                            {
                                _eventSubject.OnNext(info);
                            }
                        }
                });

        _fileWatcher.EnableRaisingEvents = true;
    }

    public IObservable<FileSystemEventArgs> FileEvents => _eventSubject;


    public void Dispose()
    {
        _fileWatcher?.Dispose();
        _eventSubject.Dispose();
        _infoSubject.Dispose();
    }
}

Использование:

var watcher = new WatcherWrapper(_path, "*.info");
// all more complicated and scenario specific filtering of events can be done here    
watcher.FileEvents.Where(x => x.ChangeType != WatcherChangeTypes.Deleted).Subscribe(x => //do stuff)

1

Я изменил способ контроля файлов в каталогах. Вместо использования FileSystemWatcher я опрашиваю местоположения в другом потоке, а затем просматриваю LastWriteTime файла.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

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


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

1

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

private void OnChanged(object source, FileSystemEventArgs e)
{
    try
    {
        using (var fs = File.OpenWrite(e.FullPath))
        {
        }
        //do your stuff
    }
    catch (Exception)
    {
        //no write access, other app not done
    }
}

Просто открытие его для записи не вызывает изменения события. Так что это должно быть безопасно.


1
FileReadTime = DateTime.Now;

private void File_Changed(object sender, FileSystemEventArgs e)
{            
    var lastWriteTime = File.GetLastWriteTime(e.FullPath);
    if (lastWriteTime.Subtract(FileReadTime).Ticks > 0)
    {
        // code
        FileReadTime = DateTime.Now;
    }
}

1
Хотя это может быть лучшим решением для задаваемого вопроса, всегда приятно добавить несколько комментариев о том, почему вы выбрали этот подход и почему вы думаете, что он работает. :)
вака

1

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

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

using System.Threading; // used for backgroundworker
using System.Diagnostics; // used for file information
private static IDictionary<string, string> fileModifiedTable = new Dictionary<string, string>(); // used to keep track of our changed events

private void fswFileWatch_Changed( object sender, FileSystemEventArgs e )
    {
        try
        {
           //check if we already have this value in our dictionary.
            if ( fileModifiedTable.TryGetValue( e.FullPath, out sEmpty ) )
            {              
                //compare timestamps      
                if ( fileModifiedTable[ e.FullPath ] != File.GetLastWriteTime( e.FullPath ).ToString() )
                {        
                    //lock the table                
                    lock ( fileModifiedTable )
                    {
                        //make sure our file is still valid
                        if ( File.Exists( e.FullPath ) )
                        {                               
                            // create a new background worker to do our task while the main thread stays awake. Also give it do work and work completed handlers
                            BackgroundWorker newThreadWork = new BackgroundWorker();
                            newThreadWork.DoWork += new DoWorkEventHandler( bgwNewThread_DoWork );
                            newThreadWork.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgwNewThread_RunWorkerCompleted );

                            // capture the path
                            string eventFilePath = e.FullPath;
                            List<object> arguments = new List<object>();

                            // add arguments to pass to the background worker
                            arguments.Add( eventFilePath );
                            arguments.Add( newEvent.File_Modified );

                            // start the new thread with the arguments
                            newThreadWork.RunWorkerAsync( arguments );

                            fileModifiedTable[ e.FullPath ] = File.GetLastWriteTime( e.FullPath ).ToString(); //update the modified table with the new timestamp of the file.
                            FILE_MODIFIED_FLAG.WaitOne(); // wait for the modified thread to complete before firing the next thread in the event multiple threads are being worked on.
                        }
                    }
                }
            }
        }
        catch ( IOException IOExcept )
        {
            //catch any errors
            postError( IOExcept, "fswFileWatch_Changed" );
        }
    }

Использовал это в одном из моих проектов. Прекрасно работает!
Тайлер Монтни

Не работает, так как события срабатывают на расстоянии друг от друга: Время последней записи: 636076274162565607 Время последней записи: 636076274162655722
Профессор программирования

1

Событие, если не спросили, жаль, что нет готовых образцов решения для F #. Чтобы исправить это, вот мой рецепт, потому что я могу, и F # - замечательный язык .NET.

Дублированные события отфильтровываются с использованием FSharp.Control.Reactiveпакета, который является просто оболочкой F # для реактивных расширений. Все, что может быть нацелено на полную структуру или netstandard2.0:

let createWatcher path filter () =
    new FileSystemWatcher(
        Path = path,
        Filter = filter,
        EnableRaisingEvents = true,
        SynchronizingObject = null // not needed for console applications
    )

let createSources (fsWatcher: FileSystemWatcher) =
    // use here needed events only. 
    // convert `Error` and `Renamed` events to be merded
    [| fsWatcher.Changed :> IObservable<_>
       fsWatcher.Deleted :> IObservable<_>
       fsWatcher.Created :> IObservable<_>
       //fsWatcher.Renamed |> Observable.map renamedToNeeded
       //fsWatcher.Error   |> Observable.map errorToNeeded
    |] |> Observable.mergeArray

let handle (e: FileSystemEventArgs) =
    printfn "handle %A event '%s' '%s' " e.ChangeType e.Name e.FullPath 

let watch path filter throttleTime =
    // disposes watcher if observer subscription is disposed
    Observable.using (createWatcher path filter) createSources
    // filter out multiple equal events
    |> Observable.distinctUntilChanged
    // filter out multiple Changed
    |> Observable.throttle throttleTime
    |> Observable.subscribe handle

[<EntryPoint>]
let main _args =
    let path = @"C:\Temp\WatchDir"
    let filter = "*.zip"
    let throttleTime = TimeSpan.FromSeconds 10.
    use _subscription = watch path filter throttleTime
    System.Console.ReadKey() |> ignore
    0 // return an integer exit code

1

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

private FileSystemWatcher watcher = new FileSystemWatcher();
...
watcher.Path = "E:\\data";
watcher.NotifyFilter = NotifyFilters.LastWrite ;
watcher.Filter = "data.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;

...

private void OnChanged(object source, FileSystemEventArgs e)
   {
    System.Timers.Timer t = new System.Timers.Timer();
    try
    {
        watcher.Changed -= new FileSystemEventHandler(OnChanged);
        watcher.EnableRaisingEvents = false;

        t.Interval = 500;
        t.Elapsed += (sender, args) => t_Elapsed(sender, e);
        t.Start();
    }
    catch(Exception ex) {
        ;
    }
}

private void t_Elapsed(object sender, FileSystemEventArgs e) 
   {
    ((System.Timers.Timer)sender).Stop();
       //.. Do you stuff HERE ..
     watcher.Changed += new FileSystemEventHandler(OnChanged);
     watcher.EnableRaisingEvents = true;
}

1

Попробуйте это, это работает нормально

  private static readonly FileSystemWatcher Watcher = new FileSystemWatcher();
    static void Main(string[] args)
    {
        Console.WriteLine("Watching....");

        Watcher.Path = @"D:\Temp\Watcher";
        Watcher.Changed += OnChanged;
        Watcher.EnableRaisingEvents = true;
        Console.ReadKey();
    }

    static void OnChanged(object sender, FileSystemEventArgs e)
    {
        try
        {
            Watcher.Changed -= OnChanged;
            Watcher.EnableRaisingEvents = false;
            Console.WriteLine($"File Changed. Name: {e.Name}");
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
        finally
        {
            Watcher.Changed += OnChanged;
            Watcher.EnableRaisingEvents = true;
        }
    }

1

Я хотел отреагировать только на последнее событие, на всякий случай, также на изменение файла linux, казалось, что файл был пустым при первом вызове, а затем снова заполнен при следующем и не возражал потерять некоторое время на всякий случай ОС решил сделать некоторые изменения файла / атрибута.

Я использую асинхронную .NET здесь, чтобы помочь мне сделать поток.

    private static int _fileSystemWatcherCounts;
    private async void OnChanged(object sender, FileSystemEventArgs e)
    {
        // Filter several calls in short period of time
        Interlocked.Increment(ref _fileSystemWatcherCounts);
        await Task.Delay(100);
        if (Interlocked.Decrement(ref _fileSystemWatcherCounts) == 0)
            DoYourWork();
    }

1

Я думаю, что лучшим решением для решения этой проблемы является использование реактивных расширений. Когда вы преобразуете событие в наблюдаемое, тогда вы можете просто добавить Throttling (..) (первоначально он назывался Debounce (..)).

Пример кода здесь

        var templatesWatcher = new FileSystemWatcher(settingsSnapshot.Value.TemplatesDirectory)
        {
            NotifyFilter = NotifyFilters.LastWrite,
            IncludeSubdirectories = true
        };

        templatesWatcher.EnableRaisingEvents = true;

        Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
                addHandler => templatesWatcher.Changed += addHandler,
                removeHandler => templatesWatcher.Changed -= removeHandler)
            .Throttle(TimeSpan.FromSeconds(5))
            .Subscribe(args =>
            {
                _logger.LogInformation($"Template file {args.EventArgs.Name} has changed");
                //TODO do something
            });

0

Я смог сделать это, добавив функцию, которая проверяет наличие дубликатов в массиве буферов.

Затем выполните действие после того, как массив не был изменен в течение X времени, используя таймер: - Сбрасывать таймер каждый раз, когда что-то записывается в буфер - Выполнять действие по тику

Это также ловит другой тип дублирования. Если вы изменяете файл внутри папки, папка также генерирует событие Change.

Function is_duplicate(str1 As String) As Boolean
    If lb_actions_list.Items.Count = 0 Then
        Return False
    Else
        Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString
        compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim

        If compStr <> str1 AndAlso compStr.parentDir <> str1 & "\" Then
            Return False
        Else
            Return True
        End If
    End If
End Function

Public Module extentions
<Extension()>
Public Function parentDir(ByVal aString As String) As String
    Return aString.Substring(0, CInt(InStrRev(aString, "\", aString.Length - 1)))
End Function
End Module

0

Это решение работало для меня в производственном приложении:

Окружающая среда:

VB.Net Framework 4.5.2

Установите свойства объекта вручную: NotifyFilter = Size

Затем используйте этот код:

Public Class main
    Dim CalledOnce = False
    Private Sub FileSystemWatcher1_Changed(sender As Object, e As IO.FileSystemEventArgs) Handles FileSystemWatcher1.Changed
            If (CalledOnce = False) Then
                CalledOnce = True
                If (e.ChangeType = 4) Then
                    ' Do task...
                CalledOnce = False
            End If
        End Sub
End Sub

Он использует ту же концепцию, что и @Jamie Krcmar, но для VB.NET
wpcoder,

0

Попробуй это!

string temp="";

public void Initialize()
{
   FileSystemWatcher _fileWatcher = new FileSystemWatcher();
  _fileWatcher.Path = "C:\\Folder";
  _fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
  _fileWatcher.Filter = "Version.txt";
  _fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
  _fileWatcher.EnableRaisingEvents = true;
}

private void OnChanged(object source, FileSystemEventArgs e)
{
   .......
if(temp=="")
{
   //do thing you want.
   temp = e.name //name of text file.
}else if(temp !="" && temp != e.name)
{
   //do thing you want.
   temp = e.name //name of text file.
}else
{
  //second fire ignored.
}

}

0

Мне пришлось объединить несколько идей из постов выше и добавить проверку блокировки файлов, чтобы она работала на меня:

FileSystemWatcher fileSystemWatcher;

private void DirectoryWatcher_Start()
{
    FileSystemWatcher fileSystemWatcher = new FileSystemWatcher
    {
        Path = @"c:\mypath",
        NotifyFilter = NotifyFilters.LastWrite,
        Filter = "*.*",
        EnableRaisingEvents = true
    };

    fileSystemWatcher.Changed += new FileSystemEventHandler(DirectoryWatcher_OnChanged);
}

private static void WaitUntilFileIsUnlocked(String fullPath, Action<String> callback, FileAccess fileAccess = FileAccess.Read, Int32 timeoutMS = 10000)
{
    Int32 waitMS = 250;
    Int32 currentMS = 0;
    FileInfo file = new FileInfo(fullPath);
    FileStream stream = null;
    do
    {
        try
        {
            stream = file.Open(FileMode.Open, fileAccess, FileShare.None);
            stream.Close();
            callback(fullPath);
            return;
        }
        catch (IOException)
        {
        }
        finally
        {
            if (stream != null)
                stream.Dispose();
        }
        Thread.Sleep(waitMS);
        currentMS += waitMS;
    } while (currentMS < timeoutMS);
}    

private static Dictionary<String, DateTime> DirectoryWatcher_fileLastWriteTimeCache = new Dictionary<String, DateTime>();

private void DirectoryWatcher_OnChanged(Object source, FileSystemEventArgs ev)
{
    try
    {
        lock (DirectoryWatcher_fileLastWriteTimeCache)
        {
            DateTime lastWriteTime = File.GetLastWriteTime(ev.FullPath);
            if (DirectoryWatcher_fileLastWriteTimeCache.ContainsKey(ev.FullPath))
            {
                if (DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath].AddMilliseconds(500) >= lastWriteTime)
                    return;     // file was already handled
            }

            DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath] = lastWriteTime;
        }

        Task.Run(() => WaitUntilFileIsUnlocked(ev.FullPath, fullPath =>
        {
            // do the job with fullPath...
        }));

    }
    catch (Exception e)
    {
        // handle exception
    }
}

0

Я подошел к проблеме двойного создания, которая игнорирует первое событие:

Private WithEvents fsw As New System.IO.FileSystemWatcher
Private complete As New List(Of String)

Private Sub fsw_Created(ByVal sender As Object, _
    ByVal e As System.IO.FileSystemEventArgs) Handles fsw.Created

    If Not complete.Contains(e.FullPath) Then
        complete.Add(e.FullPath)

    Else
        complete.Remove(e.FullPath)
        Dim th As New Threading.Thread(AddressOf hprocess)
        th.Start(e)

    End If

End Sub
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.