FileSystemWatcher Змінена подія піднімається двічі


335

У мене є додаток, де я шукаю текстовий файл і якщо в ньому внесені якісь зміни, я використовую OnChangedeventhandler для обробки події. Я використовую, 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: Недарма. Жоден із цих потенційних відповідей не забезпечує вирішення проблеми. Усі вони вирішують конкретні проблеми. Насправді жоден з них не вирішив мою конкретну проблему (мушу визнати, я не перевірив усіх).

Це вирішення, але його слід судити за якістю вирішення. Відстеження змін працює чудово, і це просто. ОП просить спосіб придушити повторювані події, і саме це дає відповіді нижче. msdn.microsoft.com/en-us/library/… Пояснює, що численні події можуть бути спричинені антивірусом чи іншим "складним файлом файлової системи" (що просто звучить як виправдання).
Тайлер Монтні

2
Нещодавно я відкрив цей випуск github.com/Microsoft/dotnet/isissue/347
Стефан Альф

2
Я створив клас, який допоможе вам отримати лише одну подію. Ви можете отримати код від github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Відповіді:


276

Я боюся, що це добре відома помилка / особливість FileSystemWatcherкласу. Це з документації класу:

Ви можете помітити в певних ситуаціях, що одна подія створення створює кілька створених подій, якими керує ваш компонент. Наприклад, якщо ви використовуєте компонент 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глобальну змінну для виявлення повторюваних подій.
Роланд

@PeterJamsmenson Події точно не ведуть водночас. Наприклад, Блокнот може генерувати декілька подій під час збереження модифікацій на диску, але ці події послідовно запускаються одна за одною протягом декількох кроків, які Блокноту потрібно виконати для збереження. Метод Бабу чудово працює.
Роланд

10
Не спрацьовує, оскільки розгорнуті події відбиваються: Останній час запису: 636076274162565607 Час останнього запису: 636076274162655722
Еш

23

Ось моє рішення, яке допомогло мені зупинити підвищення події двічі:

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

Тут я встановив NotifyFilterвластивість лише з ім'ям файлу та розміром.
watcherє моїм об'єктом FileSystemWatcher. Сподіваюсь, це допоможе.


9
Також у Блокноті я створив файл із чотирма символами: abcd у ньому. Потім я відкрив новий екземпляр Notepad і вписав ці ж чотири символи. Я вибрав файл | Збережіть як і вибрав той самий файл. Файл однаковий, а розмір та ім'я файлу не змінюються, оскільки файл має однакові чотири літери, тому це не спрацьовує.
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 ()
Phillip Kamikaze

Ні, не треба. Вони не рівні. Що стосується мого поточного проекту, то вони приблизно в мілісекунді. Я використовую (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 відповіді вище і підтримав цю. Ваша відповідь - перша, яка робить те, що повинна робити, приймаючи ОСТАННУЮ подію, а не першу. Як пояснив @Jorn, проблема полягає в тому, що файли записуються партіями. Інші рішення для мене не спрацювали.
CodingYourLife

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

5

Я створив Git repo з класом, який розширюється, 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

Одним з можливих «рубежів» може бути придушення подій, використовуючи, наприклад, Реактивне розширення:

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;
        }
    }
}

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

Спробуйте впорядкувати різні типи подій і розібратися з ними окремо, я просто пропоную можливий спосіб вирішення. Щасти.
Сяоювакс

хоча це не тестувати, я не зовсім впевнений, що це не працює для створення та видалення. він також повинен бути теоретично застосовним. Оскільки оператор fireCount ++ і if () є атомарними і не чекатимуть. навіть при двох запущених подіях, що конкурують між собою. Я здогадуюсь, що щось інше може спричинити ваші неприємності. (втрачено? Що ти маєш на увазі?)
Сяоювакс

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
Хоча це може бути найкращим рішенням поставленого питання, завжди приємно додати коментарі щодо того, чому ви вибрали такий підхід і чому ви вважаєте, що він працює. :)
waka

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 async, щоб допомогти мені зробити нарізку.

    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 = Розмір

Потім використовуйте цей код:

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.