11

次のコードがあります。


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() メソッドが 4 回実行されることを除けば、コードはうまく機能します。理由はありますか?changeType は毎回「4」です。

ありがとう。

4

15 に答える 15

16

VS.NETドキュメントの「FileSystemWatcherコンポーネントのトラブルシューティング」セクションから...

1つのアクションに対して生成された複数の作成されたイベント

特定の状況では、単一の作成イベントが、コンポーネントによって処理される複数の作成済みイベントを生成することに気付く場合があります。たとえば、FileSystemWatcherコンポーネントを使用してディレクトリ内の新しいファイルの作成を監視し、メモ帳を使用してファイルを作成してテストすると、ファイルが1つしか作成されていなくても、2つのCreatedイベントが生成される場合があります。これは、メモ帳が書き込みプロセス中に複数のファイルシステムアクションを実行するためです。メモ帳は、ファイルのコンテンツを作成してからファイル属性を作成するバッチでディスクに書き込みます。他のアプリケーションも同じように動作する場合があります。FileSystemWatcherはオペレーティングシステムのアクティビティを監視するため、これらのアプリケーションが起動するすべてのイベントが取得されます。

注:メモ帳は、他の興味深いイベント生成を引き起こす可能性もあります。たとえば、ChangeEventFilterを使用して属性の変更のみを監視するように指定し、メモ帳を使用して監視しているディレクトリ内のファイルに書き込むと、イベントが発生します。これは、メモ帳がこの操作中にファイルのアーカイブ属性を更新するためです。

于 2009-01-16T10:37:47.380 に答える
15

少し前に、私は同じ問題を経験しました。

Web を検索した結果、この問題を抱えているのは私だけではないようでした。:)だから、おそらくそれは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
于 2009-01-16T10:46:59.200 に答える
2

この問題に対する私の解決策は、新しいスレッドを開始する代わりに System.Windows.Forms.Timer を使用することを除いて、Erics に少し似ています。ファイル変更イベントが発生せずに x ミリ秒が経過した場合にのみ、変更イベントを処理するという考え方です。すべてが GUI スレッドで行われるため、スレッド化の問題はありません。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();
    }
于 2009-10-20T11:53:56.443 に答える
2

ウォッチャー変更イベント ハンドラーは、作成、削除、変更の 3 つのイベントで発生します。ファイルの名前を変更した場合にのみ、onnamed イベントが発生します。これがおそらく、4 つのアラートを受け取っている理由です。また、ほとんどのプログラムは、ファイルを閉じる前にファイルに対して複数の操作を実行します。すべてのイベントは変更と見なされるため、毎回 on_changed イベントが発生します。

于 2010-12-08T21:28:34.827 に答える
1

別の可能性があります。間違いを犯している可能性があります:)ファイルウォッチングの目的で使用する前に「Blah」クラスをインスタンス化して終了し、Disposeまたは関連するティアダウンメソッドによるRemoveHandlerの実装を忘れている可能性があります。(?)

于 2009-01-16T10:39:42.057 に答える
1

この問題と FileSystemWatcher の他の優れた機能を解決するコードをいくつか書きました。私のブログに投稿されています: http://precisionsoftware.blogspot.com/2009/05/filesystemwatcher-done-right.html

于 2009-05-13T21:58:54.657 に答える
1

私は自分にとってうまくいく簡単なクラスを作りました。他の誰かに役立つかもしれません。

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");
于 2010-04-06T14:40:46.100 に答える
1

プラットフォームに依存しないトリック:

// Class level variable
bool m_FileSystemWatcherIsMessy = true;

// inside call back
if (m_FileSystemWatcherIsMessy) {
    m_FileSystemWatcherIsMessy = false;
    return;
} else {
    m_FileSystemWatcherIsMessy = true;
}
于 2010-06-09T01:41:58.983 に答える
1

同じ問題でこのページを見つけました。また、複数のイベントを条件付きで処理するロジックを追加したとしても、後続の (重複した) イベントが発生すると、処理されるはずのコードが中断または中止され、望ましくない動作が発生します。これを回避する方法は、何らかの方法で別のスレッドにイベントハンドラーを実装することだと思います...これが理にかなっていることを願っています.

乾杯、

ニコ

于 2009-08-12T14:54:45.340 に答える
1

フレデリックのソリューションは、私がこれまでに出会った中で最高のものです。しかし、500 ミリ秒では遅すぎることがわかりました。私のアプリでは、ユーザーは 0.5 秒以内にファイルに対して 2 つのアクションを簡単に実行できるため、これを 100 に下げましたが、これまでのところ問題なく動作しています。彼の C# はちょっと面倒だった (変換しない) ので、VB バージョンを次に示します。

Public LastTimeFileWatcherEventRaised As DateTime

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

LastTimeFileWatcherEventRaised = DateTime.Now

.. handle event here
于 2012-03-05T18:00:06.053 に答える
1

パスが毎回同じであると仮定すると、ファイルを保存するために使用しているプログラムが実際に分割して保存している可能性はありますか? または、複数のBlahインスタンスが作成されていますか?


編集:ウイルス対策自動保護ソフトウェアを実行していますか? それらは、その過程でファイルに触れている可能性があります。

MSDN ドキュメントから:

一般的なファイル システム操作では、複数のイベントが発生する場合があります。たとえば、あるディレクトリから別のディレクトリにファイルを移動すると、いくつかの OnChanged イベント、いくつかの OnCreated イベント、および OnDeleted イベントが発生する可能性があります。ファイルの移動は、複数の単純な操作で構成される複雑な操作であるため、複数のイベントが発生します。同様に、一部のアプリケーション (ウイルス対策ソフトウェアなど) によって、FileSystemWatcher によって検出される追加のファイル システム イベントが発生する場合があります。


編集:または、Windowsがファイルを保存する方法に関係があるかもしれません。さまざまな変更から複数のイベントを取得している可能性があります。(1 つはサイズ用、1 つは最終書き込みタイムスタンプ用、もう 1 つは最終アクセス タイムスタンプ用、もう 1 つは...何か別のものです。)FileSystemWatcherNotifyFilterプロパティを単一の種類の変更に設定してみて、引き続き取得するかどうかを確認してください。複数のイベント。

于 2009-01-16T10:23:06.863 に答える
1

これは、最初から (Windows 3.x 以降) FindFirstChangeNotification() Win32 API の厄介な癖であり、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
        }
    }
}

その後、FileSystemWatcher とほとんど同じように FileChangeMonitor を使用できます。

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

もちろん、上記のコードは Changed イベントと NotifyFilters.LastWrite のみを処理しますが、おわかりいただけると思います。

于 2010-05-25T17:38:44.627 に答える
1

フォーム上で発生している変更イベントを表示する必要がある場合は、スレッドを使用する必要があります。エリックのソリューションは、フォームの有無にかかわらず簡単に使用でき、ソリューションを最も柔軟にするため、この点で最適です。また、複数の重複イベントを適切に処理し、同じファイルの場合にのみ重複イベントのみを食べるようにします。受け入れられた解決策では、2 つのファイルがほぼ同時に変更された場合、それらのイベントの 1 つが誤って無視される可能性があります。

于 2011-04-30T20:00:17.607 に答える
1

これは、私がこれをどのように処理するかの概念実証です。

テストするには、新しい Windows フォーム アプリケーションを作成します。フォームに、「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
于 2009-09-09T19:12:54.533 に答える