FileSystemWatcher Değiştirildi olayı iki kez yükseltildi


336

Ben bir metin dosyası arıyorum bir uygulama var ve dosyada herhangi bir değişiklik varsa OnChangedolay işlemek için eventhandler kullanıyorum . Ben kullanıyorum NotifyFilters.LastWriteTimeama yine de olay iki kez kovuluyor. İşte kod.

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

Benim durumumda, OnChangedmetin dosyasını değiştirip version.txtkaydettiğimde iki kez çağrılır .


2
@BrettRigby: Merak etmeyin. Bu potansiyel cevapların hiçbiri soruna çözüm getirmez. Hepsi belirli sorunlar için geçici çözümlerdir. Aslında, hiçbiri özel sorunumu çözmedi (itiraf etmeliyim, hepsini test etmedim).

Bu bir geçici çözümdür, ancak geçici çözümün kalitesi ile değerlendirilmelidir. Değişiklikleri takip etmek mükemmel bir şekilde çalışır ve basittir. OP, yinelenen olayları bastırmanın bir yolunu soruyor ve aşağıdaki yanıtlar bu. msdn.microsoft.com/tr-tr/library/… Birden fazla olaya virüs önleme veya diğer "karmaşık dosya sistemi" öğelerinden (bunun bir mazeret gibi geliyor) neden olabileceğini açıklar.
Tyler Montney


2
Yalnızca bir etkinlik almanıza yardımcı olacak bir sınıf oluşturdum. Kodu github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Yanıtlar:


277

Korkarım bu FileSystemWatchersınıfın iyi bilinen bir hata / özelliği . Bu, sınıfın belgelerinden alınmıştır:

Belirli durumlarda, tek bir oluşturma etkinliğinin, bileşeniniz tarafından işlenen birden çok Oluşturulan etkinlik oluşturduğunu fark edebilirsiniz. Örneğin, bir dizinde yeni dosyaların oluşturulmasını izlemek için bir FileSystemWatcher bileşeni kullanır ve daha sonra bir dosya oluşturmak için Not Defteri'ni kullanarak sınayıp, yalnızca tek bir dosya oluşturulsa bile oluşturulan iki Oluşturulan olay görebilirsiniz. Bunun nedeni, Not Defteri'nin yazma işlemi sırasında birden çok dosya sistemi eylemi gerçekleştirmesidir. Not Defteri, diske dosyanın içeriğini ve daha sonra dosya özniteliklerini oluşturan gruplar halinde yazar. Diğer uygulamalar aynı şekilde gerçekleştirilebilir. FileSystemWatcher işletim sistemi etkinliklerini izlediğinden, bu uygulamaların tetiklediği tüm olaylar alınır.

Şimdi bu metin parçası Createdolayla ilgili, ancak aynı şey diğer dosya olayları için de geçerlidir. Bazı uygulamalarda, NotifyFilterözelliği kullanarak bu sorunu çözebilirsiniz , ancak benim deneyimim, bazen bazı manuel yinelenen filtreleme (hackler) de yapmanız gerektiğini söylüyor.

Bir süre önce birkaç FileSystemWatcher ipucu içeren bir sayfa ayırdım . Kontrol etmek isteyebilirsiniz.



151

Temsilcimde şu stratejiyi kullanarak bu sorunu "düzelttim":

// 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
Bunu denedim ve bir seferde bir dosya değiştirdiysem işe yaradı, ancak aynı anda iki dosyayı değiştirdiysem (1.txt ve 2.txt kopyasına kopyalamak için 1.txt ve 2.txt gibi) yalnızca yükselirdi bir olay beklendiği gibi iki değil.
Christopher Painter

2
Birkaç ay oldu ama sonunda işin mantığını bir kilit ifadesinin içine sokan bir yöntemi çağırmakla sonuçlandığımı düşünüyorum. Bu şekilde ekstra olaylar alırsam sıraya gelene kadar sıraya girerler ve önceki yineleme her şeyi hallettiğinden beri yapacakları bir şey yoktur.
Christopher Painter

15
Bu sorunu düzeltiyor gibi görünüyor, ancak çözülmüyor. Başka bir işlem değişiklik yapıyorsa, bunları kaybedebilirsiniz, bunun işe yaramasının nedeni, diğer sürecin ES'sinin zaman uyumsuz olması ve işleminizi tamamlayana kadar izlemeyi devre dışı bırakmanızdır, böylece diğer olaylarla bir yarış koşulu oluşturursunuz. ilgi. Bu yüzden @ChristopherPainter sorununu gözlemledi.
Jf Beaulac

14
-1: İlgilendiğiniz başka bir değişiklik devre dışı bırakılırsa ne olur?
G. Stoynev

2
@ cYounes: eşyalarınızı senkronize olmayan bir şekilde yapmadığınız sürece .
David Brabant

107

İle ilgili tüm yinelenen OnChangedolaylar, söz konusu dosyadaki zaman damgası FileSystemWatcherkontrol edilerek algılanabilir ve atılabilir File.GetLastWriteTime. Şöyle ki:

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
Ben bu çözümü seviyorum, ama ben "doğru" bir şey yapmak için Rx kullandım ( "Rename"ilgilendiğiniz olayın ismini değiştirmek için):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski

4
Bir şey mi kaçırıyorum? Bunun nasıl çalışacağını anlamıyorum. Gördüğüm kadarıyla olayların eşzamanlı olarak tetiklenmesi, böylece her ikisi de yukarıdaki olayı aynı anda girerse, her ikisi de lastRead ayarlanmadan önce çalışmaya başlayacaktır.
Peter Jamsmenson

Gibi DateTimesadece milisaniye çözünürlüğe sahiptir, bu yöntem yerini bile çalışır File.GetLastWriteTimeile DateTime.Now. Durumunuza bağlı olarak, a.FullNameyinelenen olayları saptamak için genel bir değişkende de kullanabilirsiniz .
Roland

@PeterJamsmenson Olaylar tam olarak aynı anda patlamaz. Örneğin, Not Defteri değişiklikleri diske kaydederken birkaç olay oluşturabilir, ancak bu olaylar Not Defteri'nin bir kayıt işlemi için gerçekleştirmesi gereken birkaç adım sırasında art arda tetiklenir. Babu'nun yöntemi harika çalışıyor.
Roland

10
İşe yarayan olaylar birbirinden ayrıldıkça çalışmaz: Son Yazma Süresi: 636076274162565607 Son Yazma Zamanı: 636076274162655722
Asheh

23

İşte etkinliğin iki kez yapılmasını durdurmama yardımcı olan çözümüm:

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

Burada NotifyFiltersadece Dosya adı ve boyutu ile özelliği ayarladım .
watcherbenim FileSystemWatcher nesnesim. Umarım bu yardımcı olur.


9
Ayrıca, Not Defteri'nde, dört karakterli bir dosya oluşturdum: içinde abcd. Daha sonra yeni bir Not Defteri örneği açtım ve aynı dört karakteri girdim. Dosya'yı seçtim | Farklı Kaydet ve aynı dosyayı seçti. Dosya aynıdır ve dosya aynı dört harfe sahip olduğundan boyut ve dosya adı değişmez, bu nedenle tetiklenmez.
12:20 de Rhyous

30
Dosyanın boyutunu değiştirmeyen gerçek bir değişiklik yapılabilir, bu nedenle bu teknik bu durumda başarısız olur.
Lee Grissom

3
Herhangi bir anlamlı değişikliğin dosya boyutunu değiştireceğini bildiğiniz oldukça yaygın bir durumdur (örneğin, benim durumum bir günlük dosyasına ekleniyordu). Bu çözümü kullanan herkesin bu varsayımı bilmesi (ve belgelemesi) gerekirken, tam da ihtiyacım olan buydu.
GrandOpener

1
@GrandOpener: Bu her zaman doğru değildir. Benim durumumda, içeriğinin 0 veya 1 olan tek bir karakterden oluştuğu dosyaları izliyorum.

8

Benim senaryom, içinde bir Linux sunucusu bulunan bir sanal makinem olması. Windows ana bilgisayarında dosya geliştiriyorum. Ana bilgisayardaki bir klasördeki bir şeyi değiştirdiğimde tüm değişikliklerin yüklenmesini, Ftp ile sanal sunucuya senkronize edilmesini istiyorum. Bir dosyaya yazarken (bu da değiştirilecek dosyayı içeren klasörü işaretler) yinelenen değişiklik olayını ortadan kaldırmak nasıl olur:

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

Temelde dosya yazma süresi bilgilerini saklamak için bir karma tablo oluşturur. Daha sonra hashtable değiştirilmiş dosyayoluna sahipse ve zaman değeri şu anda bildirilen dosyanın değişikliği ile aynı ise, o zaman olayın kopyası olduğunu ve yoksaydığını biliyorum.


Her zaman hashtable'ı boşalttığınızı varsayıyorum.
ThunderGr

Bu ikinciye doğrudur, ancak iki değişiklik arasındaki süre bir saniyeyi geçecek kadar uzunsa başarısız olur. Üstelik daha fazla doğruluk istiyorsanız kullanabilirsiniz ToString("o")ancak daha fazla arızaya hazırlıklı olun.
Pragmateek

5
Dizeleri karşılaştırma, DateTime.Equals () kullanın
Phillip Kamikaze

Hayır, yapma. Eşit değiller. Mevcut projemde yaklaşık bir milisaniyelik bir mesafe var. (Newtime-oldtime) kullanıyorum. Toplam milisaniye <(keyfi eşik, genellikle 5 ms).
Flynn1179

8

Bu kodu deneyin:

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
Bu, zamanlayıcı yerine semafor kullanan en iyi çözümdür.
Aaron Blenkush

1
Ne semaforu? Burada sadece bir boole değişkeni görüyorum. Ayrıca, ana sorun çözülmedi: FileSystemEventHandler hala birden çok olayı tetikliyor. Ve bu kodun etkinliği nedir? if (let==false) { ... } else { let = false; }? Bunun nasıl oy kullandığı inanılmaz, bu sadece StackOverflow rozetleri meselesi olmalı.
sɐunıɔ ןɐ qɐp

8

İşte yaklaşımım:

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

Bu, dosyayı bir e-posta eki olarak gönderdiğim bir projede bu sorunu çözmek için kullandığım çözüm. Daha küçük bir zamanlayıcı aralığıyla bile iki kez tetiklenen olaydan kolayca kaçınacaktır, ancak benim durumumda 1000 saniye oldu, çünkü posta kutusunda saniyede> 1 mesajla sel basmaktan daha az değişiklik eksikti. En azından birkaç dosyanın aynı anda değiştirilmesi durumunda iyi çalışır.

Düşündüğüm başka bir çözüm, listeyi kendi MD5'lerine bir sözlük eşleme dosyaları ile değiştirmek olacaktır, bu nedenle girişi silmek, ancak değerini güncellemek zorunda kalmayacağınız için rastgele bir aralık seçmeniz gerekmez ve değişmediyse eşyalarınızı iptal edin. Dosyalar izlendikçe ve daha fazla bellek yedikçe bellekte büyüyen bir Sözlük'e sahip olmanın dezavantajı vardır, ancak izlenen dosya miktarının FSW'nin dahili arabelleğine bağlı olduğu bir yerde okudum, bu yüzden belki de kritik değil. MD5 işlem süresinin kodunuzun performansını nasıl etkileyeceğini bilmiyorsanız, dikkatli = \


Çözümünüz benim için harika çalışıyor. Yalnızca dosyayı _changedFiles Listesine eklemeyi unuttunuz. Kodun ilk bölümü şöyle görünmelidir:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey

Yukarıda 4 cevabı iptal ettim ve bunu cevapladım. Cevabınız ilkini değil, LAST etkinliğini alarak ne yapması gerektiğini ilk cevap. @Jorn tarafından açıklandığı gibi sorun dosyaları toplu olarak yazılmış olmasıdır. Diğer çözümler benim için işe yaramadı.
CodingYourLife

Çözümünüz iş parçacığı için güvenli değil. _changedFilesBirden çok iş parçacığı erişilir. Bunu düzeltmenin bir yolu ConcurrentDictionaryyerine kullanmaktır List. Diğer bir yolu akımını atamaktır Formiçin Timer.SynchronizingObjectyanı sıra, mülkiyet FileSystemWatcher.SynchronizingObjectmülkiyet.
Theodor Zoulias

5

Git FileSystemWatcherkopyasını, yalnızca kopyalama tamamlandığında olayları tetikleyen bir sınıfla oluşturdum . Son olarak değiştirilen tüm olayları atar ve yalnızca dosya okunmaya hazır olduğunda yükseltir.

FileSystemSafeWatcher'ı indirin ve projenize ekleyin.

Ardından normal olarak kullanın FileSystemWatcherve olayların ne zaman tetiklendiğini izleyin.

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

Bir dizinde bir olay oluşturulduğunda bu başarısız gibi görünüyor. Dosyayı açmadan önce bir dizin denetimi sararak işe aldım
Sam

Örnekteki yazım hatası olmasına rağmen, bu benim için uygun bir çözüm gibi görünüyor. Ancak, benim durumumda bir saniye içinde bir düzine güncelleme olabilir, bu yüzden herhangi bir değişikliği kaçırmamak için _consolidationInterval'ı önemli ölçüde düşürmek zorunda kaldım. 10 ms iyi gibi görünse de _consolidationInterval değerini 50 ms'ye ayarlarsam güncellemelerin yaklaşık% 50'sini kaybederim. Hala en uygun değeri bulmak için bazı testler yapmam gerekiyor.

Aralık benim için iyi çalışıyor gibi görünüyor. Birisinin bunu çatallamasını ve bir NuGet paketi yapmasını istiyorum.
zumalifeguard

1
Teşekkürler :) Sorunumu çözdü .. Umarım oluşturulan ve kopyalanan olaylar bu sorunu iyi çözmek için tek bir izleyici ile düzgün çalışır. stackoverflow.com/questions/55015132/…
techno

1
Bu mükemmel. Bunu projeme uyguladım ve onu kırmaya çalışmak için yaptığım her girişim yenildi. Teşekkür ederim.
Christh

4

Bu eski bir sorun olduğunu biliyorum, ama aynı sorunu vardı ve yukarıdaki çözüm hiçbiri gerçekten karşı karşıya olduğum sorun için hile yaptı. LastWriteTime ile dosya adını eşleştiren bir sözlük oluşturdum. Yani dosya sözlükte değilse, diğer akıllı kontrol işleminin son değiştirilme zamanının ne olduğunu ve sözlükte ne olduğundan farklı olduğunu görmek için işleme devam edecektir kodu çalıştırın.

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

Bu sağlam bir çözüm, ancak bir kod satırı eksik. içinde your code herebölüm ekleyebilir veya dateTimeDictionary güncellemeniz gerekir. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake

Benim için çalışmadı. Değişiklik işleyicim iki kez çağrılır ve dosya ikinci kez farklı bir zaman damgasına sahiptir. Büyük bir dosya olduğundan ve yazma işlemi ilk kez devam ettiği için olabilir. Yinelenen olayları daraltmak için bir zamanlayıcı buldum daha iyi çalıştı.
michael

3

Olası bir 'hack', Reaktif Uzantıları kullanarak olayları kısmak olacaktır:

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

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

watcher.EnableRaisingEvents = true;

Bu durumda, sistemimde yeterli olan 50 ms'ye kadar sıkıyorum, ancak daha yüksek değerler daha güvenli olmalı. (Dediğim gibi, hala bir 'hack').


.Distinct(e => e.FullPath)Başa çıkmak için daha sezgisel bulduğum bir yöntem kullandım . API'dan beklenen davranışı geri yüklediniz.
Kjellski

3

Burada çok hızlı ve basit bir çözüm var, benim için işe yarıyor ve olay bir veya iki veya daha fazla kez tetiklense de, kontrol edin:

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

İlk başta bunun benim için işe yarayacağını düşündüm, ama işe yaramadı. Dosya içeriğinin bazen üzerine yazıldığı ve diğer zamanlarda dosyanın silinip yeniden oluşturulduğu bir durum var. Dosyanızın üzerine yazılması durumunda çözümünüz çalışıyor gibi görünse de, dosyanın yeniden oluşturulması durumunda her zaman çalışmaz. İkinci durumda olaylar bazen kaybolur.

Farklı olay türlerini sıralamaya çalışın ve bunlarla ayrı ayrı ilgilenin, sadece olası bir geçici çözüm sunuyoruz. iyi şanslar.
Xiaoyuvax

test olmasa da, bu oluşturma ve silme için işe yaramaz emin değilim. FireCount ++ ve if () ifadesinin her ikisi de atomik olduğundan ve beklemeye alınmayacağından teorik olarak da uygulanmalıdır. birbiriyle yarışan iki tetiklenmiş olayla bile. Sanırım belaya sebep olan başka bir şey olmalı. (
Kayıpla

3

İşte deneyebileceğiniz yeni bir çözüm. Benim için iyi çalışıyor. Değiştirilen olayın olay işleyicisinde, istenirse işleyiciyi programcıdan tasarımcı çıktısından kaldırın, ardından programlayıcı olarak işleyiciyi geri ekleyin. misal:

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
Temsilci türünün yapıcısını çağırmanız gerekmez. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;doğru olanı yapmalı.
bartonjs

@bartonjs Bunun için teşekkürler. Tüm kurucuyu neden çağırdığımdan emin değilim. Dürüst olmak gerekirse, büyük olasılıkla bir acemi hatası. Ne olursa olsun bir düzeltme hack oldukça iyi çalıştı gibi görünüyor.
Fancy_Mammoth

2

Bunun ana nedeni, ilk etkinliğin son erişim zamanının geçerli saat olmasıydı (dosya yazma veya değiştirme zamanı). ikinci olay, dosyanın orijinal son erişim süresiydi. Kod altında çözerim.

        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 kullanarak önemli miktarda zaman geçirdim ve buradaki bazı yaklaşımlar çalışmaz. Devre dışı bırakma olayları yaklaşımını gerçekten sevdim, ancak ne yazık ki,> 1 dosya bırakılırsa işe yaramaz, ikinci dosya her zaman olmasa bile en çok özlenecektir. Bu yüzden aşağıdaki yaklaşımı kullanıyorum:

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

Bu kod benim için çalıştı.

        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

çoğunlukla gelecekteki benim için :)

Rx kullanarak bir sarıcı yazdım:

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

Kullanımı:

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

Dizinlerdeki dosyaları izleme biçimimi değiştirdim. FileSystemWatcher'ı kullanmak yerine başka bir iş parçacığındaki konumları yoklar ve dosyanın LastWriteTime değerine bakarım.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

Bu bilgileri kullanarak ve dosya yolunun bir dizinini ve en son yazma zamanını kullanarak, değişen veya belirli bir konumda oluşturulan dosyaları belirleyebilirim. Bu beni FileSystemWatcher'ın tuhaflıklarından kaldırır. Ana dezavantajı, LastWriteTime'ı ve dosyaya referansı saklamak için bir veri yapısına ihtiyacınız olmasıdır, ancak güvenilir ve uygulanması kolaydır.


9
ayrıca bir sistem olayı tarafından bilgilendirilmek yerine arka plan döngülerini yazmak zorundasınız.
Matthew

1

Yazmak için açmayı deneyebilirsiniz ve başarılı olursa, diğer uygulamanın dosyayla yapıldığını varsayabilirsiniz.

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

Sadece yazmak için açmak, değişen olayı yükseltmeyecek gibi görünüyor. Bu yüzden güvenli olmalı.


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
Bu, sorulan soruya en iyi çözüm olsa da, bu yaklaşımı neden seçtiğinizi ve neden işe yaradığını düşündüğünüz konusunda bazı yorumlar eklemek her zaman güzeldir. :)
waka

1

Mezar kazısı için özür dilerim, ama bir süredir bu konuyla mücadele ediyorum ve nihayet bu çoklu ateşli olaylarla başa çıkmanın bir yolunu buldum. Bu konuyla mücadele ederken birçok referansta kullandığım için bu konudaki herkese teşekkür etmek istiyorum.

İşte tam kodum. Dosyanın son yazılma tarihini ve saatini izlemek için bir sözlük kullanır. Bu değeri karşılaştırır ve eğer aynıysa olayları bastırır. Ardından yeni iş parçacığını başlattıktan sonra değeri ayarlar.

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

Bunu projelerimden birinde kullandım. Harika çalışıyor!
Tyler Montney

İşten atılan olaylar birbirinden ayrıldıkça çalışmaz: Son Yazma Süresi: 636076274162565607 Son Yazma Zamanı: 636076274162655722
programlama profesörü

1

Olay istenmezse, F # için hazır çözüm örnekleri olmaması bir utançtır. Bunu düzeltmek benim tarifim, çünkü yapabileceğim ve F # harika bir .NET dilidir.

Çoğaltılan olaylar, FSharp.Control.Reactivereaktif uzantılar için yalnızca bir F # sarıcı olan paket kullanılarak filtrelenir . Tam çerçeveye hedeflenebilecek her şey veya 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

Benim durumumda, ekleme yapılır yapılmaz başka bir uygulama tarafından eklenen bir metin dosyasının son satırını almanız gerekir. İşte benim çözümüm. İlk olay ortaya çıktığında, izleyicinin başkalarını yükseltmesini devre dışı bırakırım, sonra zamanlayıcıyı TimeElapsedEvent olarak çağırırım çünkü OnChanged tutamaç işlevim çağrıldığında metin dosyasının boyutuna ihtiyacım var, ancak o zamanki boyut gerçek boyut değil, yerleştirilmeden hemen önce dosyanın boyutudur. Bu yüzden doğru dosya boyutu ile devam etmek için bir süre bekleyin.

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

Bunu dene, iyi çalışıyor

  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

Sadece son olayda tepki vermek istedim, her ihtimale karşı, bir linux dosya değişikliğinde, dosyanın ilk çağrıda boş olduğunu ve bir sonraki seferde tekrar dolduğunu ve işletim sisteminin biraz zaman kaybetmesini umursamadım bazı dosya / öznitelik değişikliği yapmaya karar verdi.

Ben iş parçacığı yapmak yardımcı olmak için burada .NET zaman uyumsuz kullanıyorum.

    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

Sorunu çözmek için en iyi çözüm, reaktif uzantıları kullanmaktır Olayı gözlemlenebilir hale dönüştürdüğünüzde, sadece Throttling (..) (aslında Debounce (..))

Örnek kod burada

        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

Ben bir tampon dizisinde yinelenenleri kontrol eden bir işlev ekleyerek bunu başardı.

Daha sonra dizi bir zamanlayıcı kullanarak X süresi boyunca değiştirilmedikten sonra eylemi gerçekleştirin: - Arabelleğe her yazıldığında zamanlayıcıyı sıfırla - Kene üzerinde eylem gerçekleştir

Bu, başka bir çoğaltma türünü de yakalar. Bir klasörün içindeki bir dosyayı değiştirirseniz, klasör de bir Change olayı atar.

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

Bu çözüm benim için üretim uygulamasında çalıştı:

Çevre:

VB.Net Framework 4.5.2

Manuel nesne özelliklerini ayarlama: NotifyFilter = Boyut

Ardından bu kodu kullanın:

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 gibi aynı kavramı kullanıyor ancak VB.NET için
wpcoder

0

Bunu dene!

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

Yukarıdaki mesajlardan birkaç fikri birleştirmek ve benim için çalışmasını sağlamak için dosya kilitleme kontrolü eklemek zorunda kaldı:

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

Ben ilk olay göz ardı böyle çift oluşturma sorunu yaklaştı:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.