質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.62%

FileSystemWatcherのChangedイベントが2回発生してしまう

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,820
退会済みユーザー

退会済みユーザー

C# Visual Studio 2017でファイルを監視するプログラムを作成したいと考えております。

FilteSystemWatcherクラスを使用することで特定のファイルを監視し、イベントを発生させることができるとのことで、サンプルコードを動かしてみました。
確かにファイルを変更することでイベントは発生しましたが、イベントが2回派生してしまいます。

なぜ2回イベントが発生するのかを調べたところ、ファイルの変更方法でイベントが2回発生してしまうそうです。
ファイルの変更方法はプログラム内のStreamWriter(FilePath,False)で内容をすべて書き換えています。

私のようなファイルの変更方法ではイベントが2回発生してしまうのでしょうか?
また、イベントが1回しか発生しない書き換え方が他にあるのでしょうか?

ファイル監視のコード

public void StartWatching() {
            Watcher = new FileSystemWatcher();

            //監視するパス
            Watcher.Path = @"C:\usr";

            //ファイル名と最終書き込み時間
            Watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;

            //フィルタで監視するファイルを.txtのみにする
            Watcher.Filter = "*.txt";

            //サブディレクトリ以下も監視するか
            Watcher.IncludeSubdirectories = false;

            //変更発生時のイベントを定義する
            Watcher.Created += Changed;
            Watcher.Changed += Changed;
            Watcher.Deleted += Changed;
            Watcher.Renamed += Changed;

            //監視開始
            Watcher.EnableRaisingEvents = true;

            //必要がなくなったら監視終了(StopWatching)を呼ぶ
        }

        //ファイル監視ストップ
        public void StopWatching() {
            Watcher.EnableRaisingEvents = false;
            Watcher.Dispose();
        }

    //ファイル変更イベント
        public void Changed(object source, FileSystemEventArgs e) {
            switch (e.ChangeType) {

                case WatcherChangeTypes.Created:
                    Console.WriteLine($"新規作成: {e.FullPath}");
                    break;

                case WatcherChangeTypes.Deleted:
                    Console.WriteLine($"削除: {e.FullPath}");
                    break;

                case WatcherChangeTypes.Changed:
                    //ここのイベントが2回発生してしまう
                    Console.WriteLine($"変更: {e.FullPath}");
                    break;

                case WatcherChangeTypes.Renamed:
                    Console.WriteLine($"リネーム: {e.FullPath}");
                    break;
            }
        }

ファイル書き換えのコード

public async Task SensorChangeAsync(int i) {
            await Task.Run(() => {
                //書き換える文字列を作成
                string[] Set = File.ReadAllLines(FilePath, Encoding.Default);
                Set[i - 1] = String.Format("Sensor_{0},OFF,N", i);
         //書き換えの部分
                using (StreamWriter sw = new StreamWriter(FilePath, false)) {
                    foreach (string str in Set) {
                        sw.WriteLine(str);
                    }
                }
            });
        }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

check解決した方法

0

どうもStreamWriterが2回ファイルを書き換えていたようです。
かなり強引ですが以下のコードに変更しました。

//書き換えるテキストファイルを配列に入れる
      string[] sensorSet = File.ReadAllLines(sensorFilePath, Encoding.Default);
      sensorSet[i - 1] = String.Format("Sensor_{0},OFF,N", i);
   //読み込んで配列に入れたのでファイル自体を削除する
      File.Delete(sensorFilePath);
      //書き換えの処理 書き換えというより新規作成
      using (StreamWriter sw = new StreamWriter(sensorFilePath,false)) {
          foreach (string str in sensorSet) {
              sw.WriteLine(str);
          }
      }


書き換えるファイル自体は20文字20行程度で固定なので一旦配列に入れます。
その後読み込んだファイルを削除し、同じ名前で新規作成します。

FileSystemWatcherのイベントを変更から新規作成にすることで、イベントを1回のみにすることができました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

  1. ファイルオープン>サイズ0でファイル生成
  2. 書き込み>内部バッファにデータプール
  3. ファイルクローズ>バッファのデータを書き込み

と、1と3でファイル状態が変わるので2回のイベントになるんじゃないかと。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/17 13:37

    そうなんですが、同期処理の場合は一度しか発生しません。
    メモ帳で編集した場合も一度という情報があります。
    同期処理の場合は開いただけでは書き込まず、実際に出力があった時のみ書き込むのかもしれません。

    キャンセル

  • 2018/10/17 13:44

    それはあるかもしれませんねー
    OS内部でやってるのか.NETでバッファリングしてるのか。
    まあ、複数回のイベントでも大丈夫なように組むしかないんでしょうね

    キャンセル

  • 2018/10/17 15:46

    ご回答ありがとうございます。
    私の場合メモ帳、Atomでの書き換えを行ったところ、どちらも2回イベントが発生しました。
    また、StreamWriter(FilePath,ture)上書きではなく、追記にした場合非同期でもイベントは1回しか発生しませんでした。
    複数回イベントが発生しても問題ないように組むのも1つの考えですね。。
    大変勉強になります。ありがとうございます。

    キャンセル

0

StreamWriter がバーファーを持ってますので何回書き込むかは解りません。
複数回イベントが発生するものだとして対処するしかありません。

私はそれも含めてFilteSystemWatcherがあまり好きではないので、ファイルの更新を監視する場合は定期的に巡回するプログラムを自前で書いてます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/17 14:03

    すみません。コメントを見逃していました。
    1文字でも2回イベントが発生するということはサイズの問題ではなさそうですね。
    皆さんのご回答とても勉強になります。ありがとうございます。

    キャンセル

  • 2018/10/17 14:08

    バッファが無限に広がる仕様じゃない限りは2回だけってことは無いので、サイズに応じて回数が変わるのも間違いじゃありません。

    キャンセル

  • 2018/10/17 14:37

    確かに、バッファサイズに応じて回数も変わるみたいですね。
    ありがとうございます。勉強になります。

    キャンセル

0

気になってやってみたのですが、System.IO.File.WriteAllTextでも2回発生しました。
(NotifyFiltersをLastAccessにすると1回になりました、ただこれはアクセスなので目的にあわないとは思いますが)

公式なドキュメントまで辿りつけなかったのですが、stackoverflowではドキュメントで1回とは保証できないと書いてある、とのこと。

こちらや、その中で参照されている記事みたいな対処を自分でするしかなさそうですね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/17 15:53

    ご回答ありがとうございます。
    LastAccessでなんとかできないと思いまいたが、私の実現したいことではどうしてもLastWriteでないとできないみたいです。。。
    リンクも少し目を通してみましたが、初心者の私には少し難しいことをしているように感じました。。
    イベントが2回発生しても大丈夫なようにプログラムを作り直すか、書き込みの方法を工夫してみる方向で行きたいと考えています。
    大変勉強になりました。ありがとうございます。

    キャンセル

0

StreamWriter を作成した時点で一度、閉じた時点でもう一度変更されていますね。
開く部分のソースはここです。

internal StreamWriter(String path, bool append, Encoding encoding, int bufferSize, bool checkHost)

具体的にこの後どこで変更しているのかまで詳しく追っていませんが、ざっと見た限りでは特に何もしていないように見えます。
もしかしたら非同期処理特有の問題で、OS 内部でやっていることかもしれません。
どうしても気になるようであれば、ソースをダウンロードしてビルドし、デバッガで追ってみてはいかがでしょうか。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/17 14:10

    ご回答ありがとうございます。
    StreamWriterを作成した時点で一度変更してしまうとどうしてもイベントが2回発生しまいますね。。。
    リンクの記事も少し目を通させていただきました。
    当方プログラミング初心者なのでここに書かれていることすべてを理解できるか分かりませんが、時間をかけて読んでいこうと思います。
    大変勉強になります。ありがとうございました。

    キャンセル

  • 2018/10/17 14:19

    記事じゃなくてライブラリのソースです。
    コンパイルすると System.IO.dll ができます。
    全部読むのは大変なので読まなくていいと思いますが、自分でコンパイルすればデバッグできますから「どうしても気になる場合」は使ってください。

    キャンセル

  • 2018/10/17 14:36

    初心者の初心者ですいません。。
    なるほど、ライブラリのソースなのですね。確かにこれならデバッグできそうですね。
    ありがとうございます。どうしても気になったときに試してみます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる