Несколько событий изменения VB.NET FileSystemWatcher

У меня такой код:


Imports System.IO

Public Class Blah
    Public Sub New()
        InitializeComponent()

        Dim watcher As New FileSystemWatcher("C:\")
        watcher.EnableRaisingEvents = True

        AddHandler watcher.Changed, AddressOf watcher_Changed
    End Sub

    Private Sub watcher_Changed(ByVal sender As Object, ByVal e As FileSystemEventArgs)
        MsgBox(e.FullPath)
    End Sub
End Class

Когда я запускаю его и сохраняю изменения в файле на моем диске C, код работает отлично, за исключением того, что он выполняет метод watcher_Changed () четыре раза. Есть идеи, почему? ChangeType каждый раз равен «4».

Спасибо.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
0
25 054
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

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


Редактировать: У вас работает какое-либо антивирусное программное обеспечение для автоматической защиты? Возможно, они касаются файла в процессе.

С Документация MSDN:

Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.


Обновлено: Или, может быть, есть какое-то отношение к тому, как Windows сохраняет файл. Вы можете получить более одного события из-за разных изменений. (Один для размера, один для метки времени последней записи, один для метки времени последнего доступа и еще один для ... чего-то еще.) Попробуйте установить свойство FileSystemWatcherNotifyFilter на один тип изменения и посмотрите, продолжаете ли вы получать несколько событий.

Я пробовал две разные программы с одинаковым результатом. Насколько мне известно, у меня есть только один экземпляр Blah.

John Kurlak 16.01.2009 13:23

Неа. Я не использую никаких антивирусных программ и программ для автоматической защиты.

John Kurlak 16.01.2009 13:31

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

John Kurlak 16.01.2009 13:33

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

lc. 16.01.2009 13:35
Ответ принят как подходящий

В разделе «Устранение неполадок компонентов FileSystemWatcher» документации VS.NET ...

Multiple Created Events Generated for a Single Action

You may notice in certain situations that a single creation event generates multiple Created events that are handled by your component. For example, if you use a FileSystemWatcher component to monitor the creation of new files in a directory, and then test it by using Notepad to create a file, you may see two Created events generated even though only a single file was created. This is because Notepad performs multiple file system actions during the writing process. Notepad writes to the disk in batches that create the content of the file and then the file attributes. Other applications may perform in the same manner. Because FileSystemWatcher monitors the operating system activities, all events that these applications fire will be picked up.

Note: Notepad may also cause other interesting event generations. For example, if you use the ChangeEventFilter to specify that you want to watch only for attribute changes, and then you write to a file in the directory you are watching using Notepad, you will raise an event . This is because Notepad updates the Archived attribute for the file during this operation.

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

lc. 16.01.2009 13:43

Есть еще одна возможность, в которой вы ошибаетесь :) Возможно, вы создаете экземпляр и завершаете свой класс «Blah» перед его использованием для отслеживания файлов и забываете реализовать RemoveHandler с помощью Dispose / или любого связанного метода разрыва. (?)

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

lc. 16.01.2009 13:46

Некоторое время назад я столкнулся с той же проблемой.

После некоторого поиска в сети оказалось, что эта проблема возникла не только у меня. :) Так что, возможно, это ошибка FileSystemWatcher ...

Я решил это, отслеживая последний раз, когда был вызван обработчик событий. Если он был поднят менее xxx мс назад, я возвращаюсь из своего обработчика событий. Если кто знает более элегантное исправление; пожалуйста, дайте мне знать. :)

Вот как я работал над этим:

if ( e.ChangeType == WatcherChangeTypes.Changed )
{

    // There is a nasty bug in the FileSystemWatch which causes the 
    // events of the FileSystemWatcher to be called twice.
    // There are a lot of resources about this to be found on the Internet,
    // but there are no real solutions.
    // Therefore, this workaround is necessary: 
    // If the last time that the event has been raised is only a few msec away, 
    // we ignore it.
    if ( DateTime.Now.Subtract (_lastTimeFileWatcherEventRaised).TotalMilliseconds < 500 )
    {
        return;
    }


    _lastTimeFileWatcherEventRaised = DateTime.Now;


    .. handle event

Ха-ха ... ты думаешь, что это плохо? Я автоматически останавливаю все события после того, как одно было получено ... пока не была нажата клавиша. Думаю, я воспользуюсь вашим методом. Ха-ха-ха

John Kurlak 16.01.2009 15:07

Я написал код, который решает эту проблему и другие полезные функции FileSystemWatcher. Он размещен в моем блоге по адресу: http://precisionsoftware.blogspot.com/2009/05/filesystemwatcher-done-right.html

Этот вопрос помечен тегом «vb.net», вопрос задан в vb, и все ответы, кроме этого, находятся в vb. Я уверен, что этот «наблюдатель файловой системы, сделанный правильно» - это хорошо, но его нет в vb.net.

Jeremy 06.09.2010 12:53

@Jeremy C# можно легко преобразовать в VB.NET (за возможным исключением запросов LINQ). Погуглите "C# на VB.NET". Несмотря на то, что я обратился к этому вопросу по причине проекта C#, над которым я работаю, я лично нашел его очень полезным, несмотря на то, что я на другом языке.

Chad Levy 29.09.2010 00:16

Это хороший пост, но для WPF. Заблудился на диспетчерских линиях. Я пытаюсь заставить это работать для библиотеки классов, которая будет использоваться в приложении Windows Form. @ Эрик, не могли бы вы опубликовать преобразованный код для вашего класса в своем блоге?

Hassan Gulzar 12.12.2011 17:16

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

Ваше здоровье,

Нико

Вот доказательство концепции того, как я с этим справляюсь.

Для проверки создайте новое приложение Windows Forms. В форме добавьте многострочное текстовое поле с именем «tbMonitor». Щелкните форму правой кнопкой мыши и перейдите к Просмотр кода. Замените этот код приведенным ниже кодом. Обратите внимание, что я установил очень большое время ожидания, чтобы вы могли немного поиграть с ним. В производстве вы, вероятно, захотите сделать это число намного ниже, вероятно, около 10 или 15.

Imports System.IO
Imports System.Threading
Public Class Form1
Private Const MILLISECONDS_TO_WAIT As Integer = 1000
Private fw As FileSystemWatcher
Private Shared AccessEntries As List(Of String)
Private Delegate Sub UpdateBoxDelegate(ByVal msg As String)
Private Sub UpdateBox(ByVal msg As String)
    If tbMonitor.InvokeRequired Then
        Invoke(New UpdateBoxDelegate(AddressOf UpdateBox), New Object() {msg})
    Else
        tbMonitor.AppendText(msg + vbCrLf)
    End If
End Sub

Private Sub AccessEntryRemovalTimer(ByVal RawFileName As Object)
    UpdateBox("Sleeping to watch for " + RawFileName.ToString + " on thread ID " + Thread.CurrentThread.ManagedThreadId.ToString)
    Thread.Sleep(MILLISECONDS_TO_WAIT)
    AccessEntries.Remove(RawFileName.ToString)
    UpdateBox("Removed " + RawFileName.ToString + " in thread ID " + Thread.CurrentThread.ManagedThreadId.ToString)
End Sub

Private Sub Changed(ByVal source As Object, ByVal e As FileSystemEventArgs)
    If AccessEntries.Contains(e.Name) Then
        UpdateBox("Ignoring a " + e.ChangeType.ToString + " notification for " + e.Name + " in thread ID " + Thread.CurrentThread.ManagedThreadId.ToString)
        Return
    End If
    Dim AccessTimerThread As Thread

    AccessEntries.Add(e.Name)
    UpdateBox("Adding " + e.Name + " to the collection and starting the watch thread.")
    AccessTimerThread = New Thread(AddressOf AccessEntryRemovalTimer)
    AccessTimerThread.IsBackground = True
    AccessTimerThread.Start(e.Name)

End Sub

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    tbMonitor.ScrollBars = ScrollBars.Both
    AccessEntries = New List(Of String)
    fw = New FileSystemWatcher
    fw.Path = "C:\temp"
    fw.NotifyFilter = NotifyFilters.LastWrite Or NotifyFilters.LastAccess Or NotifyFilters.FileName
    AddHandler fw.Changed, AddressOf Changed
    AddHandler fw.Created, AddressOf Changed
    AddHandler fw.Renamed, AddressOf Changed
    fw.EnableRaisingEvents = True
End Sub

Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
    fw.EnableRaisingEvents = False
    RemoveHandler fw.Changed, AddressOf Changed
    RemoveHandler fw.Created, AddressOf Changed
    RemoveHandler fw.Renamed, AddressOf Changed
    fw.Dispose()
End Sub
End Class

Мое решение этой проблемы немного похоже на Erics, за исключением того, что я использую System.Windows.Forms.Timer вместо запуска нового потока. Идея состоит в том, что я обрабатываю событие изменения только тогда, когда прошло x мс без каких-либо событий изменения файла. Обратите внимание, что все происходит в потоке графического интерфейса, поэтому проблем с потоками нет. Я использую x = 100.

    private Dictionary<String, FileSystemEventArgs> xmlFileChangedEvents = new Dictionary<string, FileSystemEventArgs>();
    private void debugXmlWatcher_Changed(object sender, FileSystemEventArgs e)
    {
        if (!xmlFileChangedEvents.ContainsKey(e.Name))
            xmlFileChangedEvents.Add(e.Name, e);
        xmlChangeTimer.Stop();//Reset the Forms.Timer so that it times out in 100 ms
        xmlChangeTimer.Start();
    }

    private void xmlChangeTimer_Tick(object sender, EventArgs e)
    {
        foreach (FileSystemEventArgs eventArg in xmlFileChangedEvents.Values)
        {
            //
            //Handle the file changed event here
            //
        }
        xmlFileChangedEvents.Clear();
    }

Я сделал простой класс, который мне подходит. Это может быть полезно кому-то другому.

using System;
using System.IO;
using System.Timers;

namespace Demo
{
    class FileWatcher
    {
        private FileSystemWatcher watcher = new FileSystemWatcher();
        private Timer t = new Timer();

        public event EventHandler FileChanged;

        public FileWatcher()
        {
            t.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
            t.Interval = 1000;
        }

        public void Start(String path)
        {
            watcher.Path = Path.GetDirectoryName(path);
            watcher.Filter = Path.GetFileName(path);
            watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime;
            watcher.EnableRaisingEvents = true;
            watcher.Changed += new FileSystemEventHandler(watcher_Changed);
        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            if (!t.Enabled)
                t.Start();
        }

        void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            t.Stop();
            if (FileChanged != null)
                FileChanged(this, null);
        }
    }
}

Можно использовать так:

FileWatcher FileWatcher1 = new FileWatcher();
FileWatcher1.FileChanged += new EventHandler(FileWatcher1_FileChanged);
FileWatcher1.Start("c:\test.txt");

Это сводящая с ума причуда Win32 API FindFirstChangeNotification () с первого дня (начиная с Windows 3.x), и похоже, что FileSystemWatcher просто обертывает этот API. Подход с таймером (представленный выше) - это общий обходной путь.

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

public class FileChangeMonitor
{
    private FileSystemWatcher _fsw;
    DateTime _lastEventTime;

    public event FileSystemEventHandler Changed;

    public FileChangeMonitor(string path, string filter)
    {
        _fsw = new FileSystemWatcher(path, filter);
        _fsw.Changed += new FileSystemEventHandler(_fsw_Changed);
        _fsw.EnableRaisingEvents = true;
        _fsw.NotifyFilter = NotifyFilters.LastWrite;
        _fsw.IncludeSubdirectories = false;
    }

    private void _fsw_Changed(object sender, FileSystemEventArgs e)
    {
        // Fix the FindFirstChangeNotification() double-call bug
        if (DateTime.Now.Subtract(_lastEventTime).TotalMilliseconds > 100)
        {
            _lastEventTime = DateTime.Now;
            if (this.Changed != null)
                this.Changed(sender, e);  // Bubble the event
        }
    }
}

Затем вы можете использовать FileChangeMonitor почти так же, как FileSystemWatcher:

FileChangeMonitor fcm = new FileChangeMonitor(path, filter);
fsm.Changed += new FileSystemEventHandler(fsm_Changed);
...

Конечно, приведенный выше код обрабатывает только событие Changed и NotifyFilters.LastWrite, но вы поняли идею.

Независимый от платформы трюк:

// Class level variable
bool m_FileSystemWatcherIsMessy = true;

// inside call back
if (m_FileSystemWatcherIsMessy) {
    m_FileSystemWatcherIsMessy = false;
    return;
} else {
    m_FileSystemWatcherIsMessy = true;
}

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

Если вам нужно отображать события изменения, когда они происходят в форме, вам нужно использовать потоки. Решение Эрика является лучшим в этом отношении, поскольку его можно легко использовать с формой или без нее, что делает решение наиболее гибким. Он также прекрасно обрабатывает множественные повторяющиеся события и гарантирует, что он использует только повторяющиеся события, только если они предназначены для ОДНОГО ФАЙЛА. В принятом решении, если два файла изменяются почти одновременно, одно из их событий может быть неправильно проигнорировано.

Решение Фредерика, безусловно, лучшее, что я когда-либо встречал. Однако я обнаружил, что 500 миллисекунд - это слишком медленно. В моем приложении пользователь может легко выполнить два действия с файлом в течение 0,5 секунды, поэтому я снизил его до 100, и пока все работает нормально. Его C# был немного фубаром (он не конвертировался), поэтому вот версия VB:

Public LastTimeFileWatcherEventRaised As DateTime

If DateTime.Now.Subtract(LastTimeFileWatcherEventRaised).TotalMilliseconds < 100 Then Return

LastTimeFileWatcherEventRaised = DateTime.Now

.. handle event here

Я вдохновил свое решение на приведенный выше пример LAOS. Я реализую наблюдатель для папки, и каждый раз, когда он запускается, я останавливаю таймер и запускаю его снова, чтобы сбросить его. Я запускаю свои действия только по истечении таймера, что не позволяет наблюдателю дважды запускать действие для создания файла. И, как и просили, он находится в VB.Net :)

    <PermissionSet(SecurityAction.Demand, Name: = "FullTrust")> Public Sub StartWatcher()

    Dim watcher As FileSystemWatcher = New FileSystemWatcher()
    watcher.Path = _MWVM.TemplatesFolder

    'Watch for changes in LastWrite times, And the renaming of files Or directories. 
    watcher.NotifyFilter = NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName

    ' Only watch text files.
    watcher.Filter = "*.txt"

    'Define timer to 100 ms
    WatcherTimer.Interval = New TimeSpan(0, 0, 0, 0, 100) '100 ms

    ' Add event handlers.
    AddHandler watcher.Changed, AddressOf WatcherHandler
    AddHandler watcher.Created, AddressOf WatcherHandler
    AddHandler watcher.Deleted, AddressOf WatcherHandler
    AddHandler watcher.Renamed, AddressOf WatcherHandler

    ' Begin watching
    watcher.EnableRaisingEvents = True

End Sub

'Instantiate a timer which will prevent the 
Private WithEvents WatcherTimer As New System.Windows.Threading.DispatcherTimer
Private xmlFileChangedEvents As New Dictionary(Of String, FileSystemEventArgs)

Private Sub WatcherHandler(ByVal Sender As Object, ByVal e As FileSystemEventArgs)
    WatcherTimer.Stop() 'Reset the timer
    WatcherTimer.Start()
End Sub

Private Sub WatcherTimer_Tick(ByVal Sender As Object, ByVal e As EventArgs) Handles WatcherTimer.Tick
    WatcherTimer.Stop()
    PopulateMailTemplateList()
End Sub

Другие вопросы по теме