FileSystemWatcher Changed event dinaikkan dua kali

336

Saya memiliki aplikasi tempat saya mencari file teks dan jika ada perubahan pada file saya menggunakan OnChangedeventhandler untuk menangani acara tersebut. Saya menggunakan NotifyFilters.LastWriteTimetetapi masih acara dipecat dua kali. Ini kodenya.

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)
{
   .......
}

Dalam kasus saya OnChangeddisebut dua kali, ketika saya mengubah file teks version.txtdan menyimpannya.

pengguna214707
sumber
2
@ BrettRigby: Tidak heran. Tidak satu pun dari jawaban potensial ini memberikan solusi untuk masalah tersebut. Mereka semua adalah solusi untuk masalah spesifik. Bahkan, tidak ada dari mereka yang menyelesaikan masalah spesifik saya (saya harus akui, saya belum menguji semuanya).
Ini adalah solusi, tetapi harus dinilai oleh kualitas solusi. Melacak perubahan bekerja dengan sempurna, dan sederhana. OP meminta cara untuk menekan acara duplikat, dan itulah yang diberikan tanggapan di bawah ini. msdn.microsoft.com/en-us/library/... Menjelaskan bahwa banyak peristiwa dapat disebabkan oleh anti-virus, atau "hal-hal sistem file rumit" lainnya (yang hanya terdengar seperti alasan).
Tyler Montney
2
Saya baru-baru ini membuka
Stephan Ahlf
2
Saya telah membuat kelas yang membantu Anda hanya mendapatkan satu acara. Anda bisa mendapatkan kode dari github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Jawaban:

277

Saya takut ini adalah bug / fitur FileSystemWatcherkelas yang terkenal. Ini dari dokumentasi kelas:

Anda mungkin memperhatikan dalam situasi tertentu bahwa satu peristiwa kreasi menghasilkan beberapa peristiwa Dibuat yang ditangani oleh komponen Anda. Misalnya, jika Anda menggunakan komponen FileSystemWatcher untuk memantau pembuatan file baru di direktori, dan kemudian mengujinya dengan menggunakan Notepad untuk membuat file, Anda mungkin melihat dua peristiwa dibuat dihasilkan meskipun hanya satu file dibuat. Ini karena Notepad melakukan beberapa tindakan sistem file selama proses penulisan. Notepad menulis ke disk dalam kumpulan yang membuat konten file dan kemudian atribut file. Aplikasi lain dapat bekerja dengan cara yang sama. Karena FileSystemWatcher memonitor aktivitas sistem operasi, semua peristiwa yang dipicu aplikasi ini akan diambil.

Sekarang ini sedikit teks tentang Createdacara, tetapi hal yang sama berlaku untuk acara file lainnya juga. Dalam beberapa aplikasi Anda mungkin dapat menyiasatinya dengan menggunakan NotifyFilterproperti, tetapi pengalaman saya mengatakan bahwa kadang-kadang Anda harus melakukan beberapa penyaringan duplikat manual (hacks) juga.

Beberapa waktu yang lalu saya memesan halaman dengan beberapa tips FileSystemWatcher . Anda mungkin ingin memeriksanya.

Jørn Schou-Rode
sumber
6
Raymond Chen baru saja membuat blog tentang ini: Mengapa menyimpan file di Notepad memecat beberapa acara FindFirstChangeNotification?
Cody Gray
151

Saya telah "memperbaiki" masalah itu menggunakan strategi berikut dalam delegasi saya:

// 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;
   }
}
David Brabant
sumber
14
Saya mencobanya dan itu berhasil jika saya memodifikasi satu file pada satu waktu tetapi jika saya memodifikasi dua file pada suatu waktu (seperti salin 1.txt dan 2.txt untuk menyalin 1.txt dan salinan 2.txt) itu hanya akan meningkatkan satu acara, bukan dua seperti yang diharapkan.
Christopher Painter
2
Sudah beberapa bulan tapi saya pikir apa yang akhirnya saya lakukan adalah memiliki acara memanggil metode yang menempatkan logika bisnis di dalam pernyataan kunci. Dengan begitu jika saya mendapatkan acara tambahan, mereka akan mengantri sampai giliran mereka dan tidak ada yang bisa mereka lakukan sejak iterasi sebelumnya mengurus semuanya.
Christopher Painter
15
Tampaknya ini memperbaiki masalah, tetapi tidak. Jika proses lain membuat perubahan Anda mungkin kehilangan mereka, alasan tampaknya berhasil adalah karena IO dari proses lain adalah async, dan Anda menonaktifkan pemantauan sampai Anda selesai memproses Anda, sehingga menciptakan kondisi balapan dengan acara lain yang mungkin bunga. Itulah sebabnya @ChristopherPainter mengamati masalahnya.
Jf Beaulac
14
-1: Bagaimana jika perubahan lain yang Anda minati terjadi ketika dinonaktifkan?
G. Stoynev
2
@ cYounes: kecuali Anda melakukan hal-hal Anda secara tidak sinkron
David Brabant
107

Setiap OnChangedperistiwa yang digandakan dari FileSystemWatcherdapat dideteksi dan dibuang dengan memeriksa File.GetLastWriteTimecap waktu pada file yang dimaksud. Seperti itu:

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
}
BaBu
sumber
13
Saya suka solusi itu, tetapi saya telah menggunakan Rx untuk melakukan hal yang "benar" (ubah "Rename"nama acara yang Anda minati):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski
4
Apakah saya melewatkan sesuatu? Saya tidak mengerti bagaimana ini akan berhasil. Dari apa yang pernah saya lihat, event-event tersebut menyala secara bersamaan sehingga jika mereka berdua memasuki acara di atas pada saat yang sama mereka berdua akan mulai berlari sebelum lastRead diatur.
Peter Jamsmenson
Karena DateTimehanya memiliki resolusi milidetik, metode ini berfungsi bahkan jika Anda menggantinya File.GetLastWriteTimedengan DateTime.Now. Bergantung pada situasi Anda, Anda juga dapat menggunakan a.FullNamevariabel global untuk mendeteksi peristiwa duplikat.
Roland
@PeterJamsmenson Peristiwa tidak benar-benar menyala secara bersamaan. Misalnya, Notepad dapat menghasilkan beberapa acara saat menyimpan modifikasi ke disk, tetapi acara ini dipecat secara berurutan, satu demi satu, selama beberapa langkah yang perlu dilakukan Notepad untuk menghemat. Metode Babu sangat bagus.
Roland
10
Tidak berfungsi karena peristiwa yang dipicu terpisah: Waktu Tulis Terakhir: 636076274162565607 Waktu Tulis Terakhir: 636076274162655722
Asheh
23

Inilah solusi saya yang membantu saya menghentikan acara yang diangkat dua kali:

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

Di sini saya telah menetapkan NotifyFilterproperti hanya dengan Nama file dan ukuran.
watcheradalah objek FileSystemWatcher saya. Semoga ini bisa membantu.

Deepashri
sumber
9
Juga, di Notepad, saya membuat file dengan empat karakter: abcd di dalamnya. Saya kemudian membuka instance baru Notepad dan memasukkan empat karakter yang sama. Saya memilih File | Simpan Sebagai dan pilih file yang sama. File tersebut identik dan ukuran serta nama file tidak berubah, karena file tersebut memiliki empat huruf yang sama, jadi ini tidak menyala.
Rhyous
30
Mungkin saja perubahan asli dapat dilakukan yang tidak mengubah ukuran file, oleh karena itu teknik ini akan gagal dalam situasi itu.
Lee Grissom
3
Saya kira itu adalah kasus yang cukup umum di mana Anda tahu bahwa setiap perubahan yang berarti akan mengubah ukuran file (misalnya, kasus saya menambahkan ke file log). Sementara siapa pun yang menggunakan solusi ini harus menyadari (dan mendokumentasikan) asumsi itu, inilah yang saya butuhkan.
GrandOpener
1
@GrandOpener: Ini tidak selalu benar. Dalam kasus saya, saya sedang menonton file yang isinya hanya terdiri dari satu karakter, yaitu 0 atau 1.
8

Skenario saya adalah saya memiliki mesin virtual dengan server Linux di dalamnya. Saya mengembangkan file di host Windows. Ketika saya mengubah sesuatu di folder pada host saya ingin semua perubahan diunggah, disinkronkan ke server virtual melalui Ftp. Inilah cara saya menghilangkan peristiwa perubahan duplikat ketika saya menulis ke file (yang menandai folder yang berisi file yang akan dimodifikasi juga):

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

Terutama saya membuat hashtable untuk menyimpan informasi waktu menulis file. Kemudian jika hashtable memiliki filepath yang dimodifikasi dan nilainya waktu sama dengan perubahan file yang diberitahukan saat ini maka saya tahu itu adalah duplikat dari acara tersebut dan mengabaikannya.

Ikon
sumber
Saya menganggap Anda mengosongkan hashtable secara berkala.
ThunderGr
Ini akan akurat untuk yang kedua tetapi jika periode antara dua perubahan cukup lama untuk melewati satu detik itu akan gagal. Apalagi jika Anda ingin lebih akurat Anda bisa menggunakan ToString("o")tetapi bersiaplah untuk lebih banyak kegagalan.
Pragmateek
5
Jangan bandingkan string, gunakan DateTime.Equals ()
Phillip Kamikaze
Tidak, jangan. Mereka tidak setara. Dalam kasus proyek saya saat ini, jaraknya sekitar satu milidetik. Saya menggunakan (newtime-oldtime) .TotalMilliseconds <(ambang arbitrer, biasanya 5ms).
Flynn1179
8

Coba dengan kode ini:

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;
    }
}
Jamie Krcmar
sumber
1
Ini adalah solusi terbaik, menggunakan semaphore, bukan timer.
Aaron Blenkush
1
Semafor apa? Saya melihat hanya variabel boolean di sini. Lebih lanjut, masalah utama tidak terpecahkan: FileSystemEventHandler masih menjalankan beberapa peristiwa. Dan keefektifan apa yang dimiliki kode ini? if (let==false) { ... } else { let = false; }? Luar biasa bagaimana ini mendapat upvotes, ini pasti hanya masalah lencana StackOverflow.
sɐunıɔ ןɐ qɐp
8

Inilah pendekatan saya:

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

Ini adalah solusi yang saya gunakan untuk menyelesaikan masalah ini pada sebuah proyek di mana saya mengirim file sebagai lampiran dalam surat. Ini akan dengan mudah menghindari peristiwa dua kali dipecat bahkan dengan interval waktu yang lebih kecil tetapi dalam kasus saya 1000 baik-baik saja karena saya lebih bahagia dengan beberapa perubahan yang hilang daripada dengan membanjiri kotak surat dengan> 1 pesan per detik. Setidaknya itu berfungsi dengan baik jika beberapa file diubah pada waktu yang bersamaan.

Solusi lain yang saya pikirkan adalah mengganti daftar dengan file pemetaan kamus ke MD5 masing-masing, sehingga Anda tidak perlu memilih interval sewenang-wenang karena Anda tidak harus menghapus entri tetapi memperbarui nilainya, dan batalkan barang-barang Anda jika belum berubah. Ini memiliki kelemahan memiliki Kamus tumbuh dalam memori sebagai file dipantau dan makan lebih banyak dan lebih banyak memori, tetapi saya telah membaca di suatu tempat bahwa jumlah file yang dipantau tergantung pada buffer internal FSW, jadi mungkin tidak terlalu penting. Entah bagaimana waktu komputasi MD5 akan memengaruhi kinerja kode Anda, hati-hati = \

Remy Esmery
sumber
Solusi Anda sangat bagus untuk saya. Hanya saja, Anda lupa menambahkan file ke Daftar _changedFiles. Bagian pertama dari kode harus terlihat seperti ini:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey
Saya menurunkan 4 jawaban di atas dan memilih yang ini. Jawaban Anda adalah yang pertama melakukan apa yang harus dilakukan dengan mengambil acara TERAKHIR dan bukan yang pertama. Seperti yang dijelaskan oleh @Jorn, masalahnya adalah bahwa file dapat ditulis dalam batch. Solusi lain tidak berhasil untuk saya.
CodingYourLife
Solusi Anda tidak aman untuk thread. Itu _changedFilesdiakses dari beberapa utas. Salah satu cara untuk memperbaikinya adalah dengan menggunakan ConcurrentDictionarybukan List. Cara lain adalah dengan menetapkan arus Formke Timer.SynchronizingObjectproperti, serta ke FileSystemWatcher.SynchronizingObjectproperti.
Theodor Zoulias
5

Saya telah membuat repo Git dengan kelas yang diperluas FileSystemWatcheruntuk memicu peristiwa hanya ketika salinan dilakukan. Ini membuang semua peristiwa yang diubah kecuali yang terakhir dan hanya meningkatkan itu ketika file menjadi tersedia untuk dibaca.

Unduh FileSystemSafeWatcher dan tambahkan ke proyek Anda.

Kemudian gunakan seperti biasa FileSystemWatcherdan pantau saat kejadian dipicu.

var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
Menelaos Vergis
sumber
Ini tampaknya gagal ketika suatu peristiwa dimunculkan pada direktori. Saya mulai bekerja dengan membungkus cek direktori sebelum membuka file
Sam
Terlepas dari kesalahan ketik dalam contoh ini, ini tampaknya menjadi solusi yang layak untuk saya. Namun, dalam kasus saya mungkin ada selusin pembaruan dalam satu detik, jadi saya harus menurunkan _consolidationInterval secara drastis agar tidak ketinggalan perubahan. Sementara 10 ms tampaknya baik-baik saja, saya masih kehilangan sekitar 50% dari pembaruan jika saya menetapkan _consolidationInterval ke 50 ms. Saya masih harus menjalankan beberapa tes untuk menemukan nilai yang paling cocok.
_consolidationInterval tampaknya berfungsi baik untuk saya. Saya ingin seseorang melakukan fork ini dan menjadikannya paket NuGet.
zumalifeguard
1
Terima kasih :) Ini memecahkan masalah saya .. Semoga acara yang dibuat dan disalin akan bekerja dengan baik dengan satu pengamat untuk menyelesaikan masalah ini dengan baik. stackoverflow.com/questions/55015132/…
techno
1
Ini luar biasa. Saya telah mengimplementasikannya ke dalam proyek saya dan mengalahkan setiap upaya yang saya lakukan untuk mencoba memecahkannya. Terima kasih.
Christh
4

Saya tahu ini adalah masalah lama, tetapi memiliki masalah yang sama dan tidak ada solusi di atas yang melakukan trik untuk masalah yang saya hadapi. Saya telah membuat kamus yang memetakan nama file dengan LastWriteTime. Jadi jika file tidak ada dalam kamus akan melanjutkan dengan proses lain, periksa bijaksana untuk melihat kapan waktu terakhir dimodifikasi dan jika berbeda dari apa yang ada di kamus jalankan kode.

    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
                }
            }
Zardaloop
sumber
Ini adalah solusi yang solid, tetapi tidak ada baris kode. di your code herebagian ini, Anda harus menambahkan atau memperbarui dateTimeDictionary. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake
Tidak bekerja untuk saya. Handler perubahan saya dipanggil dua kali dan file memiliki stempel waktu yang berbeda untuk kedua kalinya. Bisa jadi itu adalah file yang besar dan penulisan sedang berlangsung pertama kali. Saya menemukan timer untuk menutup acara duplikat bekerja lebih baik.
michael
3

Salah satu 'peretasan' yang mungkin dilakukan adalah mencekik acara menggunakan Ekstensi Reaktif misalnya:

var watcher = new FileSystemWatcher("./");

Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
            .Throttle(new TimeSpan(500000))
            .Subscribe(HandleChangeEvent);

watcher.EnableRaisingEvents = true;

Dalam hal ini saya membatasi ke 50 ms, pada sistem saya yang cukup, tetapi nilai yang lebih tinggi harus lebih aman. (Dan seperti yang saya katakan, ini masih 'retas').

TimothyP
sumber
Saya telah menggunakan .Distinct(e => e.FullPath)yang menurut saya jauh lebih intuitif untuk dihadapi. Dan Anda mendapatkan perilaku yang dipulihkan yang diharapkan dari API.
Kjellski
3

Saya punya solusi yang sangat cepat dan sederhana di sini, itu bekerja untuk saya, dan tidak peduli peristiwa itu akan dipicu sekali atau dua kali atau lebih sesekali, lihatlah:

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
sumber
Awalnya saya pikir ini akan berhasil untuk saya, tetapi ternyata tidak. Saya mempunyai situasi, di mana konten file kadang-kadang hanya ditimpa dan lain kali file dihapus dan dibuat kembali. Meskipun solusi Anda tampaknya berfungsi jika file ditimpa, itu tidak selalu berfungsi jika file tersebut dibuat ulang. Dalam kasus terakhir, kadang-kadang peristiwa hilang.
Cobalah untuk memilah berbagai jenis acara dan mengatasinya secara terpisah, saya hanya menawarkan solusi yang mungkin. semoga berhasil.
Xiaoyuvax
Meskipun tidak mengujinya, saya tidak yakin ini tidak bekerja untuk penciptaan dan penghapusan. itu harus berlaku secara teoritis juga. Karena pernyataan fireCount ++ dan if () keduanya atomik dan tidak akan menunggu. bahkan dengan dua peristiwa yang dipicu saling bersaing. Saya kira pasti ada hal lain yang menyebabkan masalah Anda. (hilang? apa maksudmu?)
Xiaoyuvax
3

Inilah solusi baru yang bisa Anda coba. Bekerja dengan baik untuk saya. Dalam event handler untuk event yang diubah secara program, hilangkan handler dari output designer pesan jika diinginkan kemudian secara program menambahkan handler kembali. contoh:

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 );
    }
Fancy_Mammoth
sumber
1
Anda tidak perlu memanggil konstruktor tipe delegasi. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;harus melakukan hal yang benar.
bartonjs
@ Bartonjs Terima kasih untuk itu. Saya tidak yakin mengapa saya memanggil seluruh konstruktor. Jujur itu kemungkinan besar kesalahan pemula. Bagaimanapun juga sepertinya perbaikan saya berhasil cukup baik.
Fancy_Mammoth
2

Alasan utama adalah waktu akses terakhir acara pertama adalah waktu saat ini (menulis file atau mengubah waktu). kemudian acara kedua adalah waktu akses terakhir asli file. Saya memecahkan kode.

        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;
Kim Ki Won
sumber
sama dengan jawaban ini
Jean-Paul
2

Saya menghabiskan sejumlah besar waktu menggunakan FileSystemWatcher, dan beberapa pendekatan di sini tidak akan berfungsi. Saya benar-benar menyukai pendekatan penonaktifan acara, tetapi sayangnya, itu tidak berfungsi jika ada> 1 file yang dijatuhkan, file kedua akan paling banyak terlewatkan jika tidak setiap saat. Jadi saya menggunakan pendekatan berikut:

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.
}
Gino
sumber
2

Kode ini bekerja untuk saya.

        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);
    }
Neo
sumber
2

sebagian besar untuk masa depan saya :)

Saya menulis pembungkus menggunakan 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();
    }
}

Pemakaian:

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)
Krzysztof Skowronek
sumber
1

Saya telah mengubah cara saya memonitor file dalam direktori. Alih-alih menggunakan FileSystemWatcher saya polling lokasi di utas lain dan kemudian melihat LastWriteTime file.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

Dengan menggunakan informasi ini dan menyimpan indeks jalur file dan ini waktu penulisan terbaru saya dapat menentukan file yang telah berubah atau yang telah dibuat di lokasi tertentu. Ini menghapus saya dari keanehan FileSystemWatcher. Kelemahan utama adalah bahwa Anda memerlukan struktur data untuk menyimpan LastWriteTime dan referensi ke file, tetapi ini dapat diandalkan dan mudah diimplementasikan.

Wil P
sumber
9
Anda juga harus membakar siklus latar belakang alih-alih diberi tahu oleh acara sistem.
Matius Whited
1

Anda dapat mencoba membukanya untuk menulis, dan jika berhasil maka Anda dapat menganggap aplikasi lain selesai dengan file tersebut.

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
    }
}

Hanya membukanya untuk menulis tampaknya tidak meningkatkan acara yang diubah. Jadi itu harus aman.

MatteKarla
sumber
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;
    }
}
prasad
sumber
1
Meskipun ini mungkin solusi terbaik untuk pertanyaan yang diajukan, selalu menyenangkan untuk menambahkan beberapa komentar tentang mengapa Anda memilih pendekatan ini dan mengapa menurut Anda itu berhasil. :)
waka
1

Maaf untuk penggalian kubur, tapi saya sudah berjuang untuk masalah ini untuk sementara waktu sekarang dan akhirnya menemukan cara untuk menangani beberapa peristiwa yang dipecat ini. Saya ingin mengucapkan terima kasih kepada semua orang di utas ini karena saya telah menggunakannya dalam banyak referensi saat memerangi masalah ini.

Ini kode lengkap saya. Ini menggunakan kamus untuk melacak tanggal dan waktu penulisan file terakhir. Itu membandingkan nilai itu, dan jika itu sama, itu menekan peristiwa. Ini kemudian menetapkan nilai setelah memulai utas baru.

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" );
        }
    }
Serangan Biner
sumber
Digunakan ini di salah satu proyek saya. Bagus sekali!
Tyler Montney
Tidak berfungsi karena peristiwa yang dipicu terpisah: Waktu Tulis Terakhir: 636076274162565607 Waktu Tulis Terakhir: 636076274162655722
Profesor pemrograman
1

Jika tidak ditanyakan, sayang sekali tidak ada sampel solusi siap untuk F #. Untuk memperbaikinya di sini adalah resep saya, hanya karena saya bisa dan F # adalah bahasa .NET yang bagus.

Acara duplikat disaring menggunakan FSharp.Control.Reactivepaket, yang hanya merupakan pembungkus F # untuk ekstensi reaktif. Semua itu dapat ditargetkan ke kerangka penuh atau 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
python_kaa
sumber
1

Dalam kasus saya perlu mendapatkan baris terakhir dari file teks yang dimasukkan oleh aplikasi lain, segera setelah penyisipan dilakukan. Ini solusinya. Ketika acara pertama dinaikkan, saya menonaktifkan pengamat dari membesarkan orang lain, kemudian saya sebut timer TimeElapsedEvent karena ketika fungsi pegangan saya OnChanged disebut saya perlu ukuran file teks, tetapi ukuran pada waktu itu bukan ukuran sebenarnya, itu adalah ukuran file imediatelly sebelum dimasukkan. Jadi saya menunggu sebentar untuk melanjutkan dengan ukuran file yang tepat.

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;
}
André Washington
sumber
1

Coba ini, Ini berfungsi dengan baik

  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;
        }
    }
Ken
sumber
1

Saya ingin bereaksi hanya pada acara terakhir, untuk berjaga-jaga, juga pada perubahan file linux tampaknya file itu kosong pada panggilan pertama dan kemudian diisi lagi pada berikutnya dan tidak keberatan kehilangan waktu untuk berjaga-jaga jika OS memutuskan untuk melakukan beberapa perubahan file / atribut.

Saya menggunakan .NET async di sini untuk membantu saya melakukan threading.

    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();
    }
Guillermo Ruffino
sumber
1

Saya pikir solusi terbaik untuk mengatasi masalah ini adalah dengan menggunakan ekstensi reaktif Ketika Anda mengubah peristiwa menjadi dapat diamati, maka Anda bisa menambahkan Throttling (..) (awalnya disebut Debounce (..))

Kode contoh di sini

        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
            });
Cek Szy
sumber
0

Saya bisa melakukan ini dengan menambahkan fungsi yang memeriksa duplikat dalam array buffer.

Kemudian lakukan tindakan setelah array belum dimodifikasi untuk waktu X menggunakan timer: - Atur ulang timer setiap kali ada sesuatu yang ditulis ke buffer - Lakukan tindakan pada centang

Ini juga menangkap tipe duplikasi lain. Jika Anda memodifikasi file di dalam folder, folder itu juga melempar acara Ubah.

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
blindguy
sumber
0

Solusi ini bekerja untuk saya pada aplikasi produksi:

Lingkungan Hidup:

VB.Net Framework 4.5.2

Setel properti objek secara manual: NotifyFilter = Ukuran

Kemudian gunakan kode ini:

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
wpcoder
sumber
Ini menggunakan konsep yang sama seperti @Jamie Krcmar tetapi untuk VB.NET
wpcoder
0

Coba ini!

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.
}

}
LT
sumber
0

Saya harus menggabungkan beberapa ide dari posting di atas dan menambahkan cek penguncian file agar berfungsi untuk saya:

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
    }
}
HarryP
sumber
0

Saya mendekati masalah buat ganda seperti ini, yang mengabaikan acara pertama:

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
Simon Barnett
sumber