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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

Q&A

解決済

5回答

11574閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

0グッド

0クリップ

投稿2018/10/17 02:46

編集2018/10/17 02:53

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

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

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

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

ファイル監視のコード

C#

1public void StartWatching() { 2 Watcher = new FileSystemWatcher(); 3 4 //監視するパス 5 Watcher.Path = @"C:\usr"; 6 7 //ファイル名と最終書き込み時間 8 Watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; 9 10 //フィルタで監視するファイルを.txtのみにする 11 Watcher.Filter = "*.txt"; 12 13 //サブディレクトリ以下も監視するか 14 Watcher.IncludeSubdirectories = false; 15 16 //変更発生時のイベントを定義する 17 Watcher.Created += Changed; 18 Watcher.Changed += Changed; 19 Watcher.Deleted += Changed; 20 Watcher.Renamed += Changed; 21 22 //監視開始 23 Watcher.EnableRaisingEvents = true; 24 25 //必要がなくなったら監視終了(StopWatching)を呼ぶ 26 } 27 28 //ファイル監視ストップ 29 public void StopWatching() { 30 Watcher.EnableRaisingEvents = false; 31 Watcher.Dispose(); 32 } 33 34    //ファイル変更イベント 35 public void Changed(object source, FileSystemEventArgs e) { 36 switch (e.ChangeType) { 37 38 case WatcherChangeTypes.Created: 39 Console.WriteLine($"新規作成: {e.FullPath}"); 40 break; 41 42 case WatcherChangeTypes.Deleted: 43 Console.WriteLine($"削除: {e.FullPath}"); 44 break; 45 46 case WatcherChangeTypes.Changed: 47 //ここのイベントが2回発生してしまう 48 Console.WriteLine($"変更: {e.FullPath}"); 49 break; 50 51 case WatcherChangeTypes.Renamed: 52 Console.WriteLine($"リネーム: {e.FullPath}"); 53 break; 54 } 55 }

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

C#

1public async Task SensorChangeAsync(int i) { 2 await Task.Run(() => { 3 //書き換える文字列を作成 4 string[] Set = File.ReadAllLines(FilePath, Encoding.Default); 5 Set[i - 1] = String.Format("Sensor_{0},OFF,N", i); 6         //書き換えの部分 7 using (StreamWriter sw = new StreamWriter(FilePath, false)) { 8 foreach (string str in Set) { 9 sw.WriteLine(str); 10 } 11 } 12 }); 13 }

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

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

C#

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

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

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

投稿2018/10/17 08:22

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

投稿2018/10/17 03:44

Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/10/17 05:10

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

2018/10/17 05:19

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

退会済みユーザー

2018/10/17 05:36

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

0

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

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

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

投稿2018/10/17 03:40

papinianus

総合スコア12705

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/10/17 06:53

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

0

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

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

投稿2018/10/17 03:31

hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kikukiku

2018/10/17 04:18

「ファイルサイズが大きい場合など、2回以上発生する可能性がある」に1票
Zuishin

2018/10/17 04:35 編集

もちろん大きい内容を書き込む場合は複数回発生するのですが、このコードだと一文字でも二回発生しました。
hihijiji

2018/10/17 04:38

最低2回ってことでしょうね。
kikukiku

2018/10/17 04:42

そういう仕様であるとして対処するしかないですね。例えば、ファイルを書き換えるアプリは、書き換える前に拡張子をリネーム、その後ファイル内容を書き換え、その後、拡張子を元に戻すにしておいて。ウォッチャー側は、リネームのイベントのみを拾い、さらに拡張子が一致したら変更とみなす。なんてのはいいかもですね。
hihijiji

2018/10/17 04:47

FilteSystemWatcher を使うために苦労するより、必要な機能を必要なだけ作っちゃったほうがっ手っ取り早いかと。
kikukiku

2018/10/17 04:47

確かにそうですね
退会済みユーザー

退会済みユーザー

2018/10/17 06:53 編集

ご回答ありがとうございます。 バッファーについて少し調べました。確かにサイズによっては何回か書き込みする可能性がありそうですね。 ファイルの監視プログラムを自前で書くという手段も1つアリかなと思いました。 大変勉強になりました。ありがとうございました。
退会済みユーザー

退会済みユーザー

2018/10/17 05:03

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

2018/10/17 05:08

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

退会済みユーザー

2018/10/17 05:37

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

0

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

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

投稿2018/10/17 03:29

y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2018/10/17 04:37

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

2018/10/17 04:44

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

退会済みユーザー

2018/10/17 06:46

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問