FileSystemWatcherでのイベント二重起動を防止しつつ、同時更新にも対応したい

実現したいこと

使用言語:C#
開発環境:Visual Studio 2022

指定フォルダを監視し、対象フォルダ内にファイルが投入された場合に、それを入力としてバッチファイルを作成・起動するWindowsFormsアプリケーションを開発しています。バッチ起動後は、エラーコードを取得しています。
バッチファイルでは更に別のアプリケーションを起動しており、そちらがスレッドにより同時起動できるため、非同期処理をしたいと考えています。

発生している問題・分からないこと

FileSystemWatcherを使用すると、ファイルの新規作成や更新が一度だけでもイベントが複数回発生することがあります。それに対処するため、Reactive ExtensionsのThrottleにて指定秒数待機し、イベントをまとめてしまう方法をとっています。

この方法で1ファイルずつ投入されるパターンには対処できました。
しかし、複数のファイルが一度に投入された場合に、指定秒数以内にイベントがまとめて発生するためか、イベントが一つしか発生しません。

まとめると、以下のようなことを実現したいと思っています。
・FileSystemWatcherでのChangedイベント重複発生を回避したい
・ただし、複数ファイルがまとめて投入された(イベント発生は同時だがe.FullPathの値が異なっている)場合は、各ファイルで発生したイベントを全て実行させたい

該当のソースコード

C#

1using Microsoft.VisualBasic.ApplicationServices;2using Microsoft.VisualBasic.Logging;3using System.Diagnostics;4using System.Formats.Tar;5using System.Text;6using System.IO;7using static System.Net.Mime.MediaTypeNames;8using static System.Runtime.CompilerServices.RuntimeHelpers;9using System;10using System.Reactive.Linq;11 12namespace マルチスレッドSample {13 14 public partial class Form1 : Form {15 public Form1() {16 InitializeComponent();17 }18 19 private FileSystemWatcher watcher = null;20 21 public string createBat(string inputFile) {22 DateTime productionDT = DateTime.Now;23 string thNo = "-" + Thread.CurrentThread.ManagedThreadId.ToString();24 25 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);26 27 //バッチ作成28 string batFileName = "マルチスレッドsample_" + productionDT.ToString("yyyyMMddHHmmss") + thNo + ".bat";29 using(var sw = new StreamWriter(batFileName,false,Encoding.GetEncoding("shift_jis"))) {30 sw.WriteLine("説明用サンプル");31 sw.WriteLine("@echo errorlevel:%errorlevel%");32 }33 34 //ProcessStartInfoインスタンスを生成35 ProcessStartInfo processInfo = new ProcessStartInfo(batFileName){36 CreateNoWindow = true,37 RedirectStandardOutput = true,38 UseShellExecute = false 39 };40 //バッチ実行41 Process process = Process.Start(processInfo);42 //バッチファイルの実行結果を変数outputに格納する43 string output = process.StandardOutput.ReadToEnd();44 process.WaitForExit();45 46 //エラーレベルを取得するための配列47 string[] arr = output.Split("errorlevel:");48 //呼び出し元にエラーコードを返す49 return arr[1].Substring(0,1);50 }51 52 private void button1_Click(object sender,EventArgs e) {53 watcher = new FileSystemWatcher();54 55 watcher.Path = @"C:\Users\UserName\test\Input";56 watcher.NotifyFilter = 57 NotifyFilters.FileName 58 | NotifyFilters.DirectoryName 59 | NotifyFilters.LastWrite 60 | NotifyFilters.LastAccess;61 62 watcher.Filter = "*.csv";63 watcher.SynchronizingObject = this;64 65 //Rx使用66 watcher.ChangedAsObservable()67 //1秒待機68 .Throttle(TimeSpan.FromSeconds(1))69 .Subscribe(e => { 70 Task.Run(() => {71 string rtnCode = createBat(e.FullPath); 72 });73 });74 75 //監視開始76 watcher.EnableRaisingEvents = true;77 }78 } 79 static class FileSystemWatcherExtensions{80 //ChangedイベントをIObservable<TEventArgs>にする81 public static IObservable<FileSystemEventArgs> ChangedAsObservable(this FileSystemWatcher self){82 return Observable.FromEvent<FileSystemEventHandler, FileSystemEventArgs>(83 h => (_, e) => h(e),84 h => self.Changed += h,85 h => self.Changed -= h);86 }87 }88 }

試したこと・調べたこと

上記の詳細・結果

自分では、一度createBatを実行した後にe.FullPathを変数に保存し、次回実行時にその変数とe.FullPathを比較する方法を考えました。両者が一致していなければ待機せずそのままcreateBatを実行し、一致している場合のみ待機するといった具合です。
しかし、(考えてみれば当然なのですが)、この方法ですとThrottleによる待機をしない場合はやはり重複してイベントが発生します。

あくまでもThrottleでの待機は必ず行い、その中で更新されたファイル名が変わった場合と重複している場合で、処理を分岐しないといけないのだと推測しています。

不足している情報がございましたら、お手数ですがご指摘ください。
非同期処理やRxの使い方に不慣れなため、基本的な内容であればご容赦いただけますと幸いです。

補足

特になし

コメントを投稿

0 コメント