FileSystemWatcher Changedイベントが2回発生する


334

テキストファイルを探しているアプリケーションがあり、ファイルに変更が加えられている場合は、OnChangedeventhandlerを使用してイベントを処理しています。私は使用してNotifyFilters.LastWriteTimeいますが、それでもイベントが2回発生します。これがコードです。

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回呼び出されます。


2
@BrettRigby:当然だ。これらの潜在的な答えはどれも、問題の解決策を提供しません。これらはすべて、特定の問題の回避策です。実際、それらのどれも私の特定の問題を解決しませんでした(認めざるを得ません。すべてをテストしたわけではありません)。

これは回避策ですが、回避策の質によって判断する必要があります。変更の追跡は完全に機能し、それは簡単です。OPは重複するイベントを抑制する方法を求めています。それが以下の応答です。 msdn.microsoft.com/en-us/library/… 複数のイベントがウイルス対策またはその他の「複雑なファイルシステムのもの」(単なる言い訳のように聞こえる)が原因である可能性があることを説明しています。
タイラーモントニー2017年


2
イベントを1つだけ取得するのに役立つクラスを作成しました。コードはgithub.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

回答:


275

これはFileSystemWatcherクラスのよく知られたバグ/機能であると思います。これはクラスのドキュメントからです:

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

これで、このテキストはCreatedイベントに関するものですが、同じことが他のファイルイベントにも当てはまります。一部のアプリケーションでは、NotifyFilterプロパティを使用してこれを回避できる可能性がありますが、私の経験では、手動の重複フィルタリング(ハック)も実行する必要がある場合があります。

少し前に、いくつかのFileSystemWatcherのヒントを含むページを予約しました。ぜひチェックしてみてください。



150

デリゲートで次の戦略を使用して、この問題を「修正」しました。

// 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つのファイルを変更した場合はうまくいきましたが、一度に2つのファイルを変更した場合(コピー1.txtと2.txtを1.txtのコピーと2.txtのコピーにコピーするなど)、それは発生するだけです予想通り2つのイベントではなく1つのイベント。
Christopher Painter

2
数か月経ちましたが、結局のところ、ビジネスロジックをロックステートメント内に置くメソッドをイベントに呼び出させることができたと思います。そうすれば、私が追加のイベントを受け取ると、それが彼らの番になるまでキューに入れられ、前回のイテレーションがすべてを処理して以来、彼らが行うことは何もありません。
Christopher Painter

15
これで問題は解決したようですが、解決していません。別のプロセスが変更を加えている場合、それらを失う可能性があります。それが機能しているように見える理由は、他のプロセスのIOが非同期であり、処理が完了するまで監視を無効にして、他のイベントとの競合状態を作成するためです。興味を持っている。@ChristopherPainterが彼の問題を観察したのはそのためです。
Jf Beaulac 14年

14
-1:無効にしたまま、関心のある別の変更が発生した場合はどうなりますか?
G.ストイネフ2014年

2
@cYounes:あなたがない限り、自分のものを非同期に。
David Brabant

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
Iその解決策のように、私は「正しい」もの(変化行うためのRxを使用しました"Rename"イベントの名前、あなたにいる興味の):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski

4
私は何かを逃していますか?これがどのように機能するのかわかりません。私が見たところでは、イベントが同時に発生するので、両方が上記のイベントに同時に入ると、lastReadが設定される前に両方が実行を開始します。
Peter Jamsmenson 2014年

DateTimeのみミリ秒の解像度を持って、この方法は、あなたが交換する場合でも動作しますFile.GetLastWriteTimeDateTime.Now。状況によってa.FullNameは、グローバル変数でを使用して重複イベントを検出することもできます。
Roland

@PeterJamsmensonイベントは正確には同時に発生しません。たとえば、メモ帳は変更をディスクに保存するときにいくつかのイベントを生成することがありますが、これらのイベントは、メモ帳が保存のために実行する必要があるいくつかの手順中に、次々に連続して発生します。Babuの方法は非常に効果的です。
Roland

10
発生したイベントがティックごとに離れているため、機能しません。最終書き込み時間:636076274162565607最終書き込み時間:636076274162655722
Asheh

23

イベントが2回発生するのを防ぐのに役立つ私の解決策を次に示します。

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

ここでは、NotifyFilterファイル名とサイズのみでプロパティを設定しています。
watcherFileSystemWatcherの私のオブジェクトです。これがお役に立てば幸いです。


9
また、メモ帳で、abcdという4文字のファイルを作成しました。次に、メモ帳の新しいインスタンスを開いて、同じ4文字を入力しました。ファイルを選択しました| 名前を付けて保存し、同じファイルを選択します。ファイルは同じであり、サイズとファイル名は変更されません。これは、ファイルに同じ4文字が含まれているため、起動しません。
Rhyous

30
ファイルのサイズを変更しない本物の変更が行われる可能性があるため、この手法はこの状況では失敗します。
Lee Grissom 2013

3
意味のある変更がファイルサイズを変更することがわかっているのは、ごく一般的なケースだと思います(たとえば、私のケースはログファイルに追加されていました)。このソリューションを使用する人はだれでもその仮定を認識(および文書化)する必要がありますが、これがまさに私が必要としていたことです。
GrandOpener 2014

1
@GrandOpener:これは常に正しいとは限りません。私の場合は、その内容が0または1である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 2013年

これは秒単位で正確ですが、2つの変更の間の期間が1秒を通過するのに十分長い場合、失敗します。さらに、より正確にしたい場合は使用できますToString("o")が、より多くの障害に備えることができます。
Pragmateek 2013年

5
文字列を比較しないで、DateTime.Equals()を使用してください
Phillip Kamikaze

いいえ、必要ありません。彼らは等しくありません。私の現在のプロジェクトの場合、それらは約1ミリ秒離れています。私は(newtime-oldtime).TotalMilliseconds <(任意のしきい値、通常5ms)を使用しています。
Flynn1179 2017年

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

これは、ファイルをメールの添付ファイルとして送信していたプロジェクトでこの問題を解決するために使用したソリューションです。タイマー間隔を短くしても、2回発生するイベントを簡単に回避できますが、私の場合は、1秒あたり1メッセージを超えるメールボックスでフラッディングするよりも、いくつかの変更を見逃した方が楽だったので、1000で大丈夫でした。少なくとも、複数のファイルが同時に変更された場合は、問題なく動作します。

私が考えた別の解決策は、リストをそれぞれの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複数のスレッドからアクセスされます。これを修正する1つの方法は、のConcurrentDictionary代わりにを使用することですList。別の方法はFormTimer.SynchronizingObjectプロパティとプロパティに電流を割り当てることFileSystemWatcher.SynchronizingObjectです。
Theodor Zoulias

5

FileSystemWatcherコピーが完了したときにのみイベントをトリガーするように拡張するクラスを使用して、Gitリポジトリを作成しました。最後を除いてすべての変更されたイベントを破棄し、ファイルが読み取り可能になったときにのみそれを発生させます。

FileSystemSafeWatcherをダウンロードして、プロジェクトに追加します。

次に、それを通常の方法FileSystemWatcherで使用し、イベントがトリガーされるタイミングを監視します。

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

これは、ディレクトリでイベントが発生すると失敗するようです。ファイルを開く前にディレクトリチェックをラップすることで機能しました
Sam

例のタイプミスにもかかわらず、これは私にとって実行可能な解決策のようです。ただし、私の場合、1秒以内に数十回の更新が行われる可能性があるため、_consolidationIntervalを大幅に下げて、変更を見逃さないようにする必要がありました。10ミリ秒で問題ないようですが、_consolidationIntervalを50ミリ秒に設定すると、更新の約50%が失われます。私はまだいくつかのテストを実行して、最適な値を見つける必要があります。

_consolidationIntervalは私にとってはうまくいくようです。これをforkしてNuGetパッケージにしてほしい。
zumalifeguard 2018

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 2016

うまくいきませんでした。私の変更ハンドラーが2回呼び出され、ファイルのタイムスタンプが2回目に変わります。大きなファイルであり、書き込みが初めて進行中であったことが原因である可能性があります。重複したイベントを折りたたむタイマーがうまく機能することを発見しました。
マイケル

3

考えられる「ハック」の1つは、たとえば、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 2014年

3

私はここに非常に迅速で簡単な回避策があります。それは私にとってはうまくいきます、そしてイベントがたまに1回か2回、またはそれ以上トリガーされるかどうかに関係なく、チェックしてください:

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 2017

テストはしていませんが、これが作成と削除で機能しないことはよくわかりません。理論的にも適用可能である必要があります.fireCount ++とif()ステートメントはどちらもアトミックであり、待機することはありません。2つのトリガーされたイベントが互いに競合していても。他に何か問題があるのではないかと思います。(迷子になった?どういう意味?)
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 2016

@bartonjsありがとうございます。コンストラクタ全体を呼び出した理由がわかりません。正直なところ、それはおそらく初心者の間違いです。とにかく、私の修正のハックはかなりうまくいったようです。
Fancy_Mammoth 2016

2

主な理由は、最初のイベントの最終アクセス時刻が現在の時刻(ファイルの書き込みまたは変更時刻)だったためです。次に、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
    }
}

書き込みのためにそれを開くだけでは、changedイベントは発生しません。だから安全なはずです。


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

これを私のプロジェクトの1つで使用しました。よく働く!
タイラーモントニー2017年

発生したイベントがティックごとに離れているため、機能しません。最終書き込み時間: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ファイルの変更でも、最初の呼び出しでファイルが空で、次の呼び出しで再び満たされたように見え、OSの場合のために時間を失うことを気にしませんでしたファイル/属性を変更することにしました。

ここでは、.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 = 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.