O evento FileSystemWatcher Changed é gerado duas vezes

334

Eu tenho um aplicativo em que estou procurando um arquivo de texto e, se houver alguma alteração feita no arquivo, estou usando o OnChangedmanipulador de eventos para manipular o evento. Estou usando o NotifyFilters.LastWriteTimemas ainda assim o evento está sendo disparado duas vezes. Aqui está o código.

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

No meu caso, OnChangedé chamado duas vezes, quando altero o arquivo de texto version.txte o salvo.

user214707
fonte
2
@BrettRigby: Não é de admirar. Nenhuma dessas respostas em potencial fornece a solução para o problema. Todas elas são soluções alternativas para problemas específicos. De fato, nenhum deles resolveu meu problema específico (devo admitir, não testei todos eles).
É uma solução alternativa, mas deve ser julgada pela qualidade da solução alternativa. Acompanhar as alterações funciona perfeitamente e é simples. O OP está pedindo uma maneira de suprimir eventos duplicados, e é isso que as respostas abaixo fornecem. msdn.microsoft.com/en-us/library/… Explica que os vários eventos podem ser causados ​​por antivírus ou outras "coisas complicadas do sistema de arquivos" (que apenas parecem uma desculpa).
Tyler Montney
2
Recentemente, eu operei esse problema github.com/Microsoft/dotnet/issues/347
Stephan Ahlf
2
Eu criei uma classe que ajuda você a obter apenas um evento. Você pode obter o código em github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Respostas:

275

Receio que este seja um bug / recurso bem conhecido da FileSystemWatcherclasse. Isto é da documentação da classe:

Você pode perceber em certas situações que um único evento de criação gera vários eventos criados, que são tratados pelo seu componente. Por exemplo, se você usar um componente FileSystemWatcher para monitorar a criação de novos arquivos em um diretório e testá-lo usando o Bloco de Notas para criar um arquivo, poderá ver dois eventos Criados gerados, mesmo que apenas um único arquivo tenha sido criado. Isso ocorre porque o Bloco de Notas executa várias ações do sistema de arquivos durante o processo de gravação. O bloco de notas grava no disco em lotes que criam o conteúdo do arquivo e, em seguida, os atributos do arquivo. Outros aplicativos podem funcionar da mesma maneira. Como o FileSystemWatcher monitora as atividades do sistema operacional, todos os eventos disparados por esses aplicativos serão selecionados.

Agora, esse trecho de texto é sobre o Createdevento, mas o mesmo se aplica a outros eventos de arquivo. Em alguns aplicativos, você pode contornar isso usando oNotifyFilter propriedade, mas minha experiência diz que, às vezes, é necessário fazer algumas filtragem duplicada manual (hacks).

Há um tempo atrás, marquei uma página com algumas dicas do FileSystemWatcher . Você pode querer dar uma olhada.

Jørn Schou-Rode
fonte
150

Eu "corrigi" esse problema usando a seguinte estratégia em meu delegado:

// 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
fonte
14
Eu tentei isso e funcionou se eu modificasse um arquivo por vez, mas se modificasse dois arquivos por vez (como cópia 1.txt e 2.txt para copiar 1.txt e cópia de 2.txt), apenas aumentaria um evento e não dois conforme o esperado.
Christopher Painter
2
Faz alguns meses, mas acho que o que acabei fazendo foi chamar o evento de um método que coloca a lógica de negócios dentro de uma instrução de bloqueio. Dessa forma, se eu receber eventos extras, eles ficam na fila até a sua vez e não há nada a fazer desde que a iteração anterior cuidou de tudo.
Christopher Painter
15
Isso parece corrigir o problema, mas não o faz. Se outro processo estiver fazendo alterações, você poderá perdê-las, o motivo pelo qual parece funcionar é porque a IO do outro processo é assíncrona e você desativa o monitoramento até concluir o processamento, criando assim uma condição de corrida com outros eventos que podem ser de interesse. É por isso que @ChristopherPainter observou seu problema.
precisa saber é o seguinte
14
-1: e se outra alteração na qual você estiver interessado acontecer enquanto estiver desativada?
G. Stoynev
2
@cYounes: a menos que você faça suas coisas de forma assíncrona.
David Brabant
107

Qualquer OnChangedevento duplicado do FileSystemWatcherpode ser detectado e descartado, verificando o File.GetLastWriteTimecarimbo de data e hora no arquivo em questão. Igual a:

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
fonte
13
Eu gosto dessa solução, mas usei Rx para fazer a coisa "certa" (mude "Rename"para o nome do evento em que você está interessado):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski
4
Estou esquecendo de algo? Eu não entendo como isso vai funcionar. Pelo que vi, os eventos são disparados simultaneamente, portanto, se eles entrarem no evento acima ao mesmo tempo, começarão a executar antes que lastRead seja definido.
Peter Jamsmenson
Como DateTimeapenas possui resolução de milissegundos, esse método funciona mesmo se você substituir File.GetLastWriteTimepor DateTime.Now. Dependendo da sua situação, você também pode usar a a.FullNamevariável global em para detectar eventos duplicados.
Roland
@PeterJamsmenson Os eventos não são exatamente acionados simultaneamente. Por exemplo, o Bloco de Notas pode gerar vários eventos ao salvar modificações no disco, mas esses eventos são disparados seqüencialmente, um após o outro, durante as várias etapas que o Bloco de Notas precisa executar para salvar. O método de Babu funciona muito bem.
Roland
10
Não funciona, pois os eventos acionados são marcados à parte: Hora da última gravação: 636076274162565607 Hora da última gravação: 636076274162655722
Asheh
23

Aqui está minha solução que me ajudou a impedir que o evento fosse gerado duas vezes:

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

Aqui eu configurei a NotifyFilterpropriedade apenas com o nome do arquivo e o tamanho.
watcheré o meu objeto do FileSystemWatcher. Espero que isso ajude.

Deepashri
fonte
9
Além disso, no bloco de notas, criei um arquivo com quatro caracteres: abcd nele. Abri uma nova instância do bloco de notas e digitei os mesmos quatro caracteres. Eu escolhi arquivo | Salvar como e escolheu o mesmo arquivo. O arquivo é idêntico e o tamanho e o nome do arquivo não são alterados, pois o arquivo tem as mesmas quatro letras, portanto, isso não é acionado.
Rhyous 27/11/12
30
É possível que uma alteração genuína possa ser feita e não altere o tamanho do arquivo; portanto, essa técnica falharia nessa situação.
Lee Grissom
3
Eu acho que é um caso bastante comum em que você sabe que qualquer alteração significativa modificará o tamanho do arquivo (por exemplo, meu caso foi anexado a um arquivo de log). Embora qualquer pessoa que use essa solução esteja ciente (e documente) dessa suposição, era exatamente isso que eu precisava.
GrandOpener 03/03
11
@ GrandOpener: isso nem sempre é verdade. No meu caso eu estou assistindo arquivos onde seu conteúdo é composto por apenas um personagem que é ou 0 ou 1.
8

Meu cenário é que eu tenho uma máquina virtual com um servidor Linux. Estou desenvolvendo arquivos no host do Windows. Quando altero algo em uma pasta no host, desejo que todas as alterações sejam carregadas, sincronizadas no servidor virtual via FTP. É assim que elimino o evento de alteração duplicado quando escrevo em um arquivo (que sinaliza a pasta que contém o arquivo a ser modificado também):

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

Principalmente, crio uma hashtable para armazenar informações de tempo de gravação de arquivos. Então, se a hashtable tiver o caminho do arquivo modificado e seu valor de tempo for o mesmo que a alteração do arquivo notificado atualmente, sei que é a duplicata do evento e a ignoro.

Ícone
fonte
Presumo que você esvazie a hashtable periodicamente.
ThunderGr
Isso seria preciso para o segundo, mas se o período entre as duas alterações for longo o suficiente para passar um segundo, ele falhará. Além disso, se você quiser mais precisão, poderá usar, ToString("o")mas esteja preparado para mais falhas.
Pragmateek
5
Não compare seqüências de caracteres, use DateTime.Equals ()
Phillip Kamikaze
Não, não. Eles não são iguais. No caso do meu projeto atual, eles têm cerca de um milésimo de segundo. Eu uso (newtime-oldtime) .TotalMilliseconds <(limite arbitrário, geralmente 5ms).
Flynn1179
8

Tente com este código:

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
fonte
11
Esta é a melhor solução, usando um semáforo em vez de um timer.
Aaron Blenkush
11
Qual semáforo? Eu vejo apenas uma variável booleana aqui. Além disso, o principal problema não foi resolvido: o FileSystemEventHandler ainda está disparando vários eventos. E qual a eficácia desse código? if (let==false) { ... } else { let = false; }? Incrível como isso foi votado, isso deve ser apenas uma questão de emblemas StackOverflow.
sɐunıɔ ןɐ qɐp 6/04
8

Aqui está a minha abordagem:

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

Esta é a solução que usei para resolver esse problema em um projeto para o qual estava enviando o arquivo como anexo em um email. Ele evitará facilmente o evento acionado duas vezes, mesmo com um intervalo menor de timer, mas no meu caso, 1000 estava bem, pois eu estava mais feliz com poucas alterações perdidas do que com inundar a caixa de correio com> 1 mensagem por segundo. Pelo menos, funciona muito bem no caso de vários arquivos serem alterados ao mesmo tempo.

Outra solução em que pensei seria substituir a lista por um arquivo de mapeamento de dicionário para o respectivo MD5, para que você não precisasse escolher um intervalo arbitrário, pois não precisaria excluir a entrada, mas atualizar seu valor, e cancele suas coisas se não tiver mudado. Ele tem a desvantagem de ter um Dicionário crescendo na memória à medida que os arquivos são monitorados e consumindo cada vez mais memória, mas li em algum lugar que a quantidade de arquivos monitorados depende do buffer interno do FSW, talvez não seja tão crítico. Não sei como o tempo de computação MD5 afetaria o desempenho do seu código, cuidado = \

Rémy Esmery
fonte
Sua solução funciona muito bem para mim. Somente você esqueceu de adicionar o arquivo à lista _changedFiles. A primeira parte do código deve ficar assim:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey
Eu diminuí a votação para 4 respostas acima e votei positivamente para esta. Sua resposta é a primeira a fazer o que deve fazer, participando do evento ÚLTIMO e não o primeiro. Conforme explicado por @Jorn, o problema é que os arquivos são gravados em lotes. Outras soluções não funcionaram para mim.
CodingYourLife
Sua solução não é segura para threads. O _changedFilesé acessado a partir de vários threads. Uma maneira de corrigi-lo é usar um em ConcurrentDictionaryvez de List. Outra maneira é atribuir a corrente Formà Timer.SynchronizingObjectpropriedade, bem como à FileSystemWatcher.SynchronizingObjectpropriedade.
Theodor Zoulias
5

Eu criei um repositório Git com uma classe que se estende FileSystemWatcher para acionar os eventos somente quando a cópia é feita. Ele descarta todos os eventos alterados, exceto o último e o gera somente quando o arquivo fica disponível para leitura.

Baixar FileSystemSafeWatcher e adicione-o ao seu projeto.

Em seguida, use-o normalmente FileSystemWatchere monitore quando os eventos forem acionados.

var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
Menelaos Vergis
fonte
Isso parece falhar quando um evento é gerado em um diretório. Eu tenho que trabalhar envolvendo uma verificação de diretório antes de abrir o arquivo
Sam
Apesar do erro de digitação no exemplo, esta parece ser uma solução viável para mim. No entanto, no meu caso, pode haver uma dúzia de atualizações em um segundo, então tive que diminuir _consolidationInterval drasticamente para não perder nenhuma alteração. Embora 10 ms pareçam bons, ainda perderei cerca de 50% das atualizações se eu definir _consolidationInterval como 50 ms. Ainda preciso executar alguns testes para encontrar o valor que melhor se ajusta.
_consolidationInterval parece funcionar bem para mim. Gostaria que alguém bifurque isso e faça dele um pacote NuGet.
Zumalifeguard 11/1118
11
Obrigado :) Ele resolveu meu problema. Espero que os eventos criados e copiados funcionem corretamente com um único observador para resolver esse problema. stackoverflow.com/questions/55015132/…
techno
11
Isto e excelente. Eu o implementei no meu projeto e ele venceu todas as tentativas que fiz para tentar quebrá-lo. Obrigado.
Christh
4

Sei que esse é um problema antigo, mas tinha o mesmo problema e nenhuma das soluções acima realmente resolveu o problema que estava enfrentando. Eu criei um dicionário que mapeia o nome do arquivo com o LastWriteTime. Portanto, se o arquivo não estiver no dicionário, prosseguirá com o processo de outra forma, para verificar quando foi a última vez modificada e se é diferente do que está no dicionário, execute o código.

    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
fonte
Esta é uma solução sólida, mas está faltando uma linha de código. na your code hereseção, você deve adicionar ou atualizar o dateTimeDictionary. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake
não funcionou para mim. Meu manipulador de alterações é chamado duas vezes e o arquivo tem um carimbo de data e hora diferente na segunda vez. Pode ser porque é um arquivo grande e a gravação estava em andamento na primeira vez. Encontrei um cronômetro para recolher eventos duplicados que funcionaram melhor.
5606 Michael
3

Um possível "hack" seria limitar os eventos usando as Extensões reativas, por exemplo:

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

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

watcher.EnableRaisingEvents = true;

Nesse caso, estou acelerando para 50ms, no meu sistema, o suficiente, mas valores mais altos devem ser mais seguros. (E como eu disse, ainda é um 'hack').

TimothyP
fonte
Eu usei o .Distinct(e => e.FullPath)que eu acho muito mais intuitivo para lidar. E você recuperou o comportamento esperado da API.
Kjellski
3

Eu tenho uma solução muito rápida e simples aqui, funciona para mim e, independentemente do evento ser acionado uma ou duas vezes ou mais vezes, ocasionalmente, confira:

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
fonte
No começo, pensei que isso funcionaria para mim, mas não. Tenho uma situação em que o conteúdo dos arquivos às vezes é apenas substituído e outras vezes o arquivo é excluído e recriado. Embora sua solução pareça funcionar no caso de o arquivo ser substituído, ela nem sempre funciona no caso de o arquivo ser recriado. No último caso, os eventos às vezes se perdem.
Tente resolver diferentes tipos de eventos e lidar com eles separadamente, apenas ofereço uma solução possível. boa sorte.
Xiaoyuvax 15/02
embora não o teste, não tenho certeza de que isso não funcione para criação e exclusão. deve também ser teoricamente aplicável. Desde a declaração fireCount ++ e if (), são atômicas e não serão colocadas em espera. mesmo com dois eventos acionados competindo entre si. Eu acho que deve haver algo mais causar o seu problema. (? por perdido o que você quer dizer?)
Xiaoyuvax
3

Aqui está uma nova solução que você pode tentar. Funciona bem para mim. No manipulador de eventos do evento alterado, remova programaticamente o manipulador da saída do designer, se desejado, e adicione programaticamente o manipulador novamente. exemplo:

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
fonte
11
Você não precisa chamar o construtor do tipo delegado. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;deve fazer a coisa certa.
precisa saber é o seguinte
@ Bartonjs Obrigado por isso. Não sei por que invoquei o construtor inteiro. Honestamente, é mais provável que seja um erro de novato. Independentemente disso, parece que meu truque de correção funcionou bastante bem.
Fancy_Mammoth 23/09/16
2

O principal motivo foi o último horário de acesso do primeiro evento foi o horário atual (gravação do arquivo ou horário alterado). o segundo evento era a última hora de acesso original do arquivo. Eu resolvo sob código.

        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
fonte
o mesmo que esta resposta
Jean-Paul
2

Passei uma quantidade significativa de tempo usando o FileSystemWatcher, e algumas das abordagens aqui não funcionarão. Gostei muito da abordagem de eventos incapacitantes, mas, infelizmente, não funciona se houver> 1 arquivo sendo descartado; o segundo arquivo será perdido mais, se não o tempo todo. Então, eu uso a seguinte abordagem:

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
fonte
2

Este código funcionou para mim.

        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
fonte
2

principalmente para o futuro me :)

Eu escrevi um wrapper usando 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();
    }
}

Uso:

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
fonte
1

Mudei a maneira como monitore arquivos em diretórios. Em vez de usar o FileSystemWatcher, pesquiso locais em outro encadeamento e, em seguida, observe o LastWriteTime do arquivo.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

Usando essas informações e mantendo um índice de um caminho de arquivo e a última hora de gravação, posso determinar os arquivos que foram alterados ou que foram criados em um local específico. Isso me remove das estranhezas do FileSystemWatcher. A principal desvantagem é que você precisa de uma estrutura de dados para armazenar o LastWriteTime e a referência ao arquivo, mas é confiável e fácil de implementar.

Wil P
fonte
9
assim como você precisa gravar ciclos em segundo plano em vez de ser notificado por um evento do sistema.
Matthew Whited
1

Você pode tentar abri-lo para gravação e, se for bem-sucedido, poderá assumir que o outro aplicativo é feito com o arquivo.

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

Apenas abri-lo para gravação parece não aumentar o evento alterado. Portanto, deve ser seguro.

MatteKarla
fonte
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
fonte
11
Embora essa possa ser a melhor solução para a pergunta, é sempre bom adicionar alguns comentários sobre por que você escolheu essa abordagem e por que acha que ela funciona. :)
waka
1

Sinto muito pela escavação grave, mas estou lutando contra esse problema há um tempo e finalmente encontrei uma maneira de lidar com esses vários eventos disparados. Gostaria de agradecer a todos neste tópico, pois o usei em muitas referências ao combater esse problema.

Aqui está o meu código completo. Ele usa um dicionário para rastrear a data e hora da última gravação do arquivo. Ele compara esse valor e, se for o mesmo, suprime os eventos. Em seguida, define o valor após o início do novo thread.

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" );
        }
    }
BinaryAssault
fonte
Usei isso em um dos meus projetos. Funciona bem!
Tyler Montney
Não funciona, pois os eventos acionados são marcados à parte: Hora da última gravação: 636076274162565607 Hora da última gravação: 6360762741626555522
Professor de programação
1

Caso não seja solicitado, é uma pena que não haja amostras de soluções prontas para F #. Para corrigir isso aqui está a minha receita, apenas porque eu posso e o F # é uma maravilhosa linguagem .NET.

Eventos duplicados são filtrados usando o FSharp.Control.Reactivepacote, que é apenas um wrapper F # para extensões reativas. Tudo isso pode ser direcionado para a estrutura completa ou 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
fonte
1

No meu caso, preciso obter a última linha de um arquivo de texto que é inserido por outro aplicativo, assim que a inserção for concluída. Aqui está a minha solução. Quando o primeiro evento é disparado, desabilito o observador de gerar outros e chamo o timer TimeElapsedEvent porque, quando minha função de identificador OnChanged é chamada, preciso do tamanho do arquivo de texto, mas o tamanho naquele momento não é o tamanho real, é o tamanho do arquivo imediatamente antes da inserção. Então, espero um pouco para prosseguir com o tamanho certo do arquivo.

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
fonte
1

Tente isso, está funcionando bem

  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
fonte
1

Eu queria reagir apenas no último evento, apenas no caso, também em uma alteração de arquivo linux, parecia que o arquivo estava vazio na primeira chamada e depois preenchido novamente na próxima e não me importei em perder algum tempo, apenas no caso do sistema operacional. decidiu fazer alguma alteração no arquivo / atributo.

Estou usando o .NET assíncrono aqui para me ajudar a realizar a segmentação.

    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
fonte
1

Eu acho que a melhor solução para resolver o problema é usar extensões reativas. Quando você transforma um evento em observável, basta adicionar Throttling (..) (originalmente chamado Debounce (..))

Exemplo de código aqui

        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
fonte
0

Consegui fazer isso adicionando uma função que verifica duplicatas em uma matriz de buffer.

Em seguida, execute a ação depois que o array não tiver sido modificado pelo tempo X usando um timer: - Redefina o timer toda vez que algo for gravado no buffer - Execute a ação no tick

Isso também captura outro tipo de duplicação. Se você modificar um arquivo dentro de uma pasta, a pasta também lançará um evento Change.

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
cego
fonte
0

Esta solução funcionou para mim no aplicativo de produção:

Meio Ambiente:

VB.Net Framework 4.5.2

Defina manualmente as propriedades do objeto: NotifyFilter = Size

Então use este código:

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
fonte
Ele usa o mesmo conceito como @Jamie Krcmar mas para VB.NET
wpcoder
0

Tente isso!

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
fonte
0

Eu tive que combinar várias idéias das postagens acima e adicionar uma verificação de bloqueio de arquivos para que funcionasse para mim:

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
fonte
0

Abordei o problema de criação dupla como este, que ignora o primeiro evento:

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
fonte