Una guida pratica a FileSystemWatcher: gestire gli eventi persi e duplicati

· Aggiornato il: · · FileSystemWatcher, C#, .NET, Sviluppo Windows, Integrazione file, Progettazione

FileSystemWatcher è la prima API che viene visualizzata durante il monitoraggio delle modifiche ai file in .NET su Windows. Fornisce comodamente creazioni, modifiche, eliminazioni e ridenominazioni di file e directory come eventi, ma se tratti Created o Changed come notifiche di completamento, verrai regolarmente bruciato da eventi persi, notifiche duplicate e lettura di file scritti a metà.

In questo articolo, organizziamo come utilizzare FileSystemWatcher e le sue insidie, presupponendo un’integrazione principalmente basata su file con .NET su Windows. Per i concetti sottostanti di mutua esclusione, vedere anche Fondamenti di esclusione reciproca per l’integrazione basata su file: best practice per i blocchi dei file e le rivendicazioni atomiche.

In realtà, Created può attivarsi mentre un file è ancora in fase di copia e non è assolutamente garantito che Changed si attivi solo una volta. Quando le modifiche si concentrano in una finestra breve, il buffer interno può traboccare e le singole modifiche vengono eliminate.

Quindi il nocciolo del progetto è questo.

  • Le notifiche sono trigger
  • La verità risiede nella nuova scansione della directory
  • La proprietà deriva da una rivendicazione atomica
  • L’idempotenza cattura ciò che resta

Nel corpo dell’articolo, esaminiamo le trappole del cablaggio di FileSystemWatcher nell’integrazione dei file con questa mentalità.

Il codice in questo articolo è pubblicato in GitHub come set di esempi completo compilabile ed eseguibile (una libreria, una demo della console eseguita su una directory temporanea e unit test che creano e modificano effettivamente i file per verificare gli eventi).

filesystemwatcher-safe-basics - komurasoft-blog-samples (GitHub)

Sommario

  1. Prima la conclusione (in una riga)
    • 1.1. Un esempio minimo che funziona
  2. Modelli di malintesi comuni con FileSystemWatcher (diagrammi)
    • 2.1. Trattare Created come una notifica di completamento
    • 2.2. Fidarsi del Conte e dell’Ordine di Changed
    • 2.3. Perdere le modifiche a causa dell’overflow del buffer interno
  3. Anti-modelli
    • 3.1. Elaborazione direttamente all’interno del gestore eventi
    • 3.2. Tentativo di ricostruire il vero stato dal flusso di eventi
    • 3.3. Trattare “No More Changed” come Completamento
    • 3.4. Credere in un InternalBufferSize più grande risolve tutto
    • 3.5. Registrare Error e ignorarlo
  4. Migliori pratiche
    • 4.1. Piega le notifiche in “Richieste di nuova scansione”
    • 4.2. Rendi esplicito il completamento sul lato mittente
    • 4.3. Il ricevente accetta un reclamo atomicamente
    • 4.4. Nuova scansione completa all’avvio / overflow / riconnessione
    • 4.5. Assumere idempotenza
  5. Pseudocodice (estratti)
    • 5.1. Il tipico modello di fallimento
    • 5.2. Un esempio nella giusta direzione (approssimativamente abbozzato)
  6. Una guida approssimativa alla scelta
  7. Conclusione
  8. Riferimenti

1. Prima la conclusione (in una riga)

  • Gli eventi FileSystemWatcher non sono notifiche di completamento: indicano che qualcosa è cambiato
  • Created / Changed / Renamed possono duplicarsi, arrivare in un ordine che non ti aspettavi e cadere in caso di overflow
  • I gestori eventi sono più stabili quando non svolgono lavori pesanti e accodano solo una richiesta di nuova scansione
  • Il rilevamento del completamento deve essere reso esplicito tramite file / manifest temp -> close -> rename / replace o done
  • Con più lavoratori, è necessario presentare un reclamo atomicamente prima di leggere
  • L’accordatura InternalBufferSize è un aiuto. Alla fine, le nuove scansioni complete e l’idempotenza sono ciò che funziona

In breve: non trattare FileSystemWatcher come “un flusso storico veritiero”. Le cose si rompono molto meno se le notifiche non rimangono altro che un segnale di “è ora di andare a vedere”.

1.1. Un esempio minimo che funziona

Per chi non ha mai toccato FileSystemWatcher, ecco la forma minima che mostra solo il percorso felice. Dai prossimi capitoli in poi si parlerà delle trappole che nascono dal fatto che anche queste poche righe “funzionano”.

// C# / .NET 8 app console. Forma minima per verificare solo che arrivino notifiche
using System.IO;

using var watcher = new FileSystemWatcher(@"C:\incoming")
{
    Filter = "*.csv",
    NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
};

watcher.Created += (_, e) => Console.WriteLine($"Created: {e.FullPath}");
watcher.Changed += (_, e) => Console.WriteLine($"Changed: {e.FullPath}");
watcher.Renamed += (_, e) => Console.WriteLine($"Renamed: {e.OldFullPath} -> {e.FullPath}");
watcher.Error += (_, e) => Console.WriteLine($"Error: {e.GetException().Message}");

watcher.EnableRaisingEvents = true; // da qui inizia la sorveglianza
Console.WriteLine("Premi Invio per uscire");
Console.ReadLine();

Anche in questa forma minima, tre punti aiutano a non perdere tempo.

  • Fino a quando non si imposta EnableRaisingEvents = true, non arriva nemmeno un evento. Aver registrato i gestori non basta
  • La durata del watcher coincide con quella dell’app. Se esce dallo scope di una variabile locale e viene distrutto, le notifiche si fermano lì. Se deve rimanere attivo, tienilo in un campo o in un altro posto che sopravvive
  • Il valore predefinito di NotifyFilter è la combinazione LastWrite | FileName | DirectoryName (vedi FileSystemWatcher.NotifyFilter Property nei riferimenti). Scegliere esplicitamente cosa raccogliere aiuta a non lasciare il dubbio in seguito

Ed è importante ricordare che questo codice verifica solo “che gli eventi arrivino”. Non dice se possiamo leggere il file al momento del Created, né se le notifiche vadano perse. Da qui inizia il vero argomento.

2. Modelli di malintesi comuni con FileSystemWatcher (diagrammi)

2.1. Trattare Created come una notifica di completamento

Questa è la mina più evidente. Durante le copie e i trasferimenti, Created si attiva nel momento in cui il file viene creato e successivamente possono seguire uno o più eventi Changed.

ReceiverFileSystemWatcherwatched dirSenderReceiverFileSystemWatcherwatched dirSenderCopy still in progressMissing rows / corrupt JSON / corrupt ZIPCreates orders.csvCreatedOnCreatedOpens and reads orders.csvWrites the restChangedChanged

Created può significare “il nome è diventato visibile”, ma non garantisce che “ora sia sicuro da leggere”. Se si equiparano i due, si entra nella stessa miniera della sezione 2.1 dell’articolo precedente, solo per un percorso diverso.

2.2. Fidarsi del Conte e dell’Ordine di Changed

Non è garantito che Changed spari esattamente una volta. Anche le operazioni ordinarie come lo spostamento o il salvataggio possono essere visualizzate come più eventi. Oltre a ciò, puoi anche apprendere i dettagli dei software antivirus e degli indicizzatori.

FileSystemWatcherAV / indexerwatched dirSaving appFileSystemWatcherAV / indexerwatched dirSaving appNot guaranteed to be once-only or in this orderStarts saving report.xlsxCreatedChangedRename from a temp fileRenamedChangedScan / attribute accessChanged

Aspettative come “uno Changed significa fatto” o “niente tocca il file dopo Renamed” sono piuttosto precarie.

Note aggiuntive:

  • La ridenominazione di un file può produrre un evento Changed
  • RenamedEventArgs.Name può essere null quando OS non può correlare i vecchi / nuovi nomi
  • I file nascosti non sono esenti. “È un nome temporaneo nascosto, quindi non sarà visibile” non è valido
  • Se la directory controllata stessa viene rinominata, tale modifica non viene segnalata

2.3. Perdere le modifiche a causa dell’overflow del buffer interno

FileSystemWatcher ha un buffer interno. Quando le modifiche si concentrano in una breve finestra, questa va in overflow e le singole notifiche vengono eliminate.

NoMolte modifiche in una breve finestraLe notifiche si accumulano nel buffer internoL'elaborazione riesce a stare al passo?Elabora gli eventi individuali in ordineOverflowEvento di erroreNon fidarti più della completezza della cronologia per eventoNuova scansione completa della directory

Il punto importante qui è che “un overflow significa perdere solo un evento” non è garantito. La completezza dell’intero flusso di eventi individuali diventa sospetta, quindi è meglio semplicemente riesaminare l’intero quadro.

3. Anti-modelli

3.1. Elaborazione direttamente all’interno del gestore eventi

Ciò attribuisce troppo peso – rilevamento del completamento e acquisizione della proprietà – agli eventi stessi.

watcher.Created += (_, e) =>
{
    using var stream = File.OpenRead(e.FullPath);
    Import(stream); // May still be mid-copy
};

watcher.Error += (_, e) =>
{
    Console.WriteLine(e.GetException()); // Just printing it
};

Ci sono due problemi.

  • Al momento del Created, i contenuti potrebbero essere incompleti
  • Non è possibile ripristinare i dati in caso di errori o overflow

Un gestore eventi dà il meglio di sé quando solleva semplicemente una richiesta di nuova scansione e restituisce immediatamente il risultato. Se avvii pesanti aggiornamenti I / O o DB qui, ti strangoli durante i burst.

3.2. Tentativo di ricostruire il vero stato dal flusso di eventi

Il design di “aggiungi a un dizionario su Created, aggiorna su Changed, rimuovi su Deleted, reinserisci su Renamed” sembra pulito a prima vista. Ma una volta che entrano duplicati, divisioni, overflow e interferenze esterne, la contabilità smette gradualmente di sommarsi.

switch (e.ChangeType)
{
    case WatcherChangeTypes.Created:
        state[e.FullPath] = Pending;
        break;
    case WatcherChangeTypes.Changed:
        state[e.FullPath] = Modified;
        break;
    case WatcherChangeTypes.Deleted:
        state.Remove(e.FullPath);
        break;
}

Piuttosto che lottare in questa direzione, è più forte ricontrollare ogni volta i file effettivi sul disco. Ciò che conta nell’integrazione dei file è trovare correttamente ciò che può essere elaborato in questo momento, non ricostruire fedelmente la cronologia degli eventi.

3.3. Trattare “No More Changed” come completamento

Questo design ha lo stesso odore dell’articolo precedente “le dimensioni hanno smesso di cambiare, quindi è fatta”. Sembra conveniente, ma determina il completamento indovinando.

if (lastChangedAt + TimeSpan.FromSeconds(10) < DateTime.UtcNow)
{
    return Ready;
}

I casi in cui ciò fallisce includono:

  • Una copia di file di grandi dimensioni viene interrotta a metà
  • L’app di invio salva in più fasi
  • Le notifiche appaiono ritardate su una condivisione di rete
  • Un processo esterno riscrive successivamente gli attributi o i timestamp

Il completamento è più stabile se dichiarato esplicitamente piuttosto che indovinato.

3.4. Credere in un InternalBufferSize più grande risolve tutto

L’accordatura InternalBufferSize è importante, ma non è il cuore del design.

  • Il valore predefinito è 8192 byte
  • Non può scendere al di sotto di 4096 byte e non può superare 64 KB
  • Il buffer utilizza memoria non paginata, quindi aumentarlo non è così casuale come sembra

In altre parole, anche al 64 KB, una notifica scoppiata oltre mette fine alla storia. E non risolve il problema “è una notifica di completamento?” problema anche solo di un millimetro.

Prima di ampliare il buffer, ci sono alcune cose da fare.

  • Restringi il campo dell’orologio con Filter / Filters
  • Mantenere NotifyFilter al minimo necessario
  • Non impostare IncludeSubdirectories su true con noncuranza
  • Mantenere leggeri i gestori di eventi
  • Aggiungi nuove scansioni complete e idempotenza

3.5. Registrare Error e ignorarlo

Error non è il tipo di notifica che puoi “vedere occasionalmente e scrollarti di dosso”. Overflow del buffer e problemi nel continuare a osservare emergono qui.

watcher.Error += (_, e) =>
{
    _logger.LogError(e.GetException(), "watcher error");
    // Ending here means noticing the loss but never recovering
};

Come minimo, vuoi arrivare così lontano.

  • Richiedi una nuova scansione completa
  • Se hai dubbi sulla continuazione della visione, valuta la possibilità di ricreare l’osservatore
  • Rendere la rielaborazione idempotente, partendo dal presupposto che gli eventi siano andati perduti

4. Migliori pratiche

4.1. Piega le notifiche in “Richieste di nuova scansione”

Il cablaggio di Created / Changed / Deleted / Renamed / Error ciascuno direttamente in una logica aziendale separata rovina la chiarezza. Per prima cosa riuniscili tutti in un unico tipo di segnale: “vai a vedere”.

Created / Changed / Deleted / Renamedscan requestError / overflowstartupRescan the directoryEnumerate ready candidatesAttempt a claim

Punti di implementazione:

  • Nel gestore eventi, fai poco più che impostare dirty = true e inviare un segnale
  • Concentrare la scansione in un unico operatore
  • Durante i burst, coalesce per circa 100-300 ms, quindi esegue la scansione una volta
  • Se durante una scansione arrivano più notifiche, esegui nuovamente la scansione in seguito

In questo modo, che arrivino cinque o cinquanta eventi, l’azione finale è unitaria: “guarda i dossier attuali e trova quello che è pronto”.

4.2. Rendi esplicito il completamento sul lato mittente

Se controlli anche il mittente, la correzione del protocollo di pubblicazione evita lo sforzo sul rilevamento del completamento sul lato FileSystemWatcher.

Il percorso provato, ancora una volta, è:

  • Scrivi l’intero contenuto sotto un nome temp
  • close esso
  • rename / replace sullo stesso file system
  • Se necessario, inserisci per ultimo un file / manifest done
Write the full content to data.tmpflush / closerename / replace to data.csvPlace data.done / manifest.jsonReceiver watches only final names or done files

Come nell’articolo precedente, ma è qui che sta il vero vantaggio. Il modo giusto di vedere FileSystemWatcher non è come uno strumento che inventa il completamento, ma come uno strumento che nota prima il completamento dichiarato esplicitamente.

4.3. Il ricevente accetta un reclamo atomicamente

Anche quando una nuova scansione trova un candidato pronto, l’accesso diretto alla lettura consente a più operatori di acquisirlo contemporaneamente. Quindi accetta un reclamo atomicamente prima dell’elaborazione.

processing/worker2processing/worker1incomingscannerprocessing/worker2processing/worker1incomingscannerOnly the one that succeeds first holds ownershipFinds order-123rename order-123rename order-123

Come accennato nell’articolo precedente, la ridenominazione incoming -> processing/<worker>/ è la più chiara. Raggruppare il payload + manifest + file ausiliari in un’unica directory è particolarmente conveniente, poiché puoi quindi richiedere il pacchetto.

incoming/
  order-123/
    payload.csv
    manifest.json

Con questo, una singola ridenominazione della directory del bundle diventa proprietaria.

4.4. Nuova scansione completa all’avvio / overflow / riconnessione

Questo è abbastanza importante.

  • I file posizionati prima dell’avvio dell’app non vengono rilevati dagli eventi
  • Una volta che si verifica un overflow, diventa difficile fidarsi del singolo flusso di eventi
  • Con condivisioni di rete e disconnessioni temporanee in gioco, è più lecito presumere che sia mancato “qualcosa nel mezzo”.

Quindi, almeno in questi momenti, dovrebbe essere eseguita una nuova scansione completa.

  • All’avvio
  • Alla ricezione di Error
  • Subito dopo aver ricreato l’osservatore
  • Periodicamente, a titolo assicurativo, a intervalli fissi

La filosofia qui: “l’osservatore è un accenno ai delta; la nuova scansione è il recupero della coerenza”.

4.5. Assumere idempotenza

Con FileSystemWatcher finirai per esaminare lo stesso target più volte. Non si tratta di un bug: è più stabile accettarlo come parte del progetto.

Concretamente funziona così.

  • Inserisci un IdempotencyKey nel manifest
  • Se già elaborato, non rieseguire gli effetti collaterali
  • Rendere verificabili gli stati archiviati / DB-registrati / inviati
  • Assicurarsi che una nuova scansione completa non sia altro che “guardare di nuovo in sicurezza le stesse cose”

Cercare di costruire esattamente una volta solo a partire dagli eventi diventa rapidamente doloroso. Accettare almeno una volta e chiudere il ciclo con l’idempotenza è la posizione più forte nella pratica.

5. Pseudocodice (estratti)

5.1. Il tipico modello di fallimento

using var watcher = new FileSystemWatcher(incomingDir)
{
    Filter = "*.csv",
    IncludeSubdirectories = false,
    EnableRaisingEvents = true,
    InternalBufferSize = 64 * 1024
};

watcher.Created += (_, e) =>
{
    // Assumes Created = completion notification
    ProcessFile(e.FullPath);
};

watcher.Changed += (_, e) =>
{
    // It keeps firing, so just process again
    ProcessFile(e.FullPath);
};

watcher.Error += (_, e) =>
{
    Console.WriteLine(e.GetException());
    // No recovery
};

Ci sono quattro problemi.

  • Created / Changed sono collegati direttamente all’elaborazione aziendale
  • Non è previsto il rilevamento del completamento
  • Nessuna nuova scansione completa in caso di overflow
  • Nessun meccanismo per interrompere l’elaborazione ripetuta dello stesso file

5.2. Un esempio nella giusta direzione (approssimativamente abbozzato)

private readonly SemaphoreSlim _scanSignal = new(0, int.MaxValue);
private int _scanRequested = 0;
private int _fullRescanRequested = 0;

void OnAnyChange(object? sender, FileSystemEventArgs e)
{
    RequestScan(full: false);
}

void OnRenamed(object? sender, RenamedEventArgs e)
{
    RequestScan(full: false);
}

void OnError(object? sender, ErrorEventArgs e)
{
    Log(e.GetException());
    RequestScan(full: true);
}

void RequestScan(bool full)
{
    if (full)
    {
        Interlocked.Exchange(ref _fullRescanRequested, 1);
    }

    if (Interlocked.Exchange(ref _scanRequested, 1) == 0)
    {
        _scanSignal.Release();
    }
}

async Task ScannerLoopAsync(CancellationToken cancellationToken)
{
    RequestScan(full: true); // startup scan

    while (!cancellationToken.IsCancellationRequested)
    {
        await _scanSignal.WaitAsync(cancellationToken);

        // Coalesce notification bursts a little
        await Task.Delay(TimeSpan.FromMilliseconds(200), cancellationToken);

        Interlocked.Exchange(ref _scanRequested, 0);
        bool full = Interlocked.Exchange(ref _fullRescanRequested, 0) == 1;

        foreach (var bundle in EnumerateReadyBundles(incomingDir, full))
        {
            var claimedPath = Path.Combine(processingDir, bundle.Name);

            if (!TryClaimByRename(bundle.Path, claimedPath))
            {
                continue; // Another worker claimed it first
            }

            var manifest = ReadManifest(Path.Combine(claimedPath, "manifest.json"));

            if (AlreadyProcessed(manifest.IdempotencyKey))
            {
                MoveToArchive(claimedPath, archiveDir);
                continue;
            }

            ProcessBundle(claimedPath);
            RecordProcessed(manifest.IdempotencyKey);
            MoveToArchive(claimedPath, archiveDir);
        }

        if (Volatile.Read(ref _scanRequested) == 1)
        {
            _scanSignal.Release(); // Do not drop notifications that arrived mid-scan
        }
    }
}

Ciò che conta in questo esempio è il flusso, non i dettagli dell’API.

  • Piega le notifiche in richieste di scansione
  • Trova ciò che è pronto eseguendo la scansione
  • Presenta un reclamo
  • Controllare l’idempotenza
  • Elabora, registra e sposta nell’archivio

Gli eventi FileSystemWatcher qui non sono altro che un trigger.

6. Una guida approssimativa alla scelta

  • Lavoratore ricevente singolo / puoi anche correggere il mittente Inizia con temp -> close -> rename e una scansione all’avvio. Solo questo ti porta abbastanza lontano.

  • Lavoratori riceventi multipli Aggiungi la ridenominazione della rivendicazione incoming -> processing oltre a quanto sopra.

  • Alta frequenza, ricco di notifiche Restringi Filter / NotifyFilter / IncludeSubdirectories e riduci al minimo i gestori di eventi. L’accordatura InternalBufferSize viene dopo.

  • Gli overflow danneggiati / eventi persi non sono accettabili Basati su nuove scansioni complete e, se ciò non bastasse, non scommettere solo su FileSystemWatcher. Se utilizzi solo Windows, anche il journal delle modifiche USN è un’opzione.

  • Non puoi controllare come scrive l’altro sistema Piuttosto che nascondere le condizioni di completamento con ipotesi, considerare innanzitutto se il protocollo di pubblicazione può essere negoziato. In caso contrario, abbassare il livello di garanzia e appoggiarsi a un design di ricezione idempotente.

Gli ultimi due elementi sono criteri di prelievo abbastanza importanti. FileSystemWatcher è utile, ma non è un rilevatore di verità onnipotente.

In cosa differisce il USN change journal

Il USN change journal è un registro delle modifiche mantenuto da NTFS a livello di volume. A differenza delle notifiche di directory come FileSystemWatcher, che se l’app non è in esecuzione al momento del cambiamento non vengono ricevute, il change journal conserva il record sul lato volume, quindi le modifiche avvenute mentre l’app era ferma possono essere rilette successivamente a partire dalla posizione (USN) letta in precedenza. Anche nella documentazione Microsoft, tra i punti deboli delle notifiche di directory è citata la necessità di tenere l’app sempre in esecuzione, e come soluzione viene proposto proprio il change journal.

D’altra parte, il carico aumenta.

  FileSystemWatcher USN change journal
Unità di monitoraggio Directory specificata (+ sottodirectory) Intero volume. L’app deve filtrare il range desiderato
Quando l’app è ferma Non si sa. Si compensa con full rescan Si possono rileggere i record
Perdite Overflow del buffer interno Oltre il limite del journal, i record vecchi vengono eliminati
Requisiti Solo le API .NET Handle del volume e chiamate FSCTL_*. Creazione / eliminazione del journal richiede privilegi di amministratore

Quindi, è un’opzione da considerare quando i requisiti includono “non può essere sempre in esecuzione” o “voglio recuperare anche le modifiche avvenute durante l’arresto”. Se questi requisiti non esistono, FileSystemWatcher + full rescan è un’implementazione più semplice.

7. Conclusione

FileSystemWatcher non sostituisce le notifiche di completamento. La verità non è nel flusso degli eventi ma in ciò che è visibile sul disco in questo momento. Rendi esplicito il completamento tramite file / manifest temp -> close -> rename / replace o done e decidi la proprietà accettando una rivendicazione in modo atomico. È lì che vive il cuore del design.

Elaborare immediatamente il Created, fidarsi del conteggio o dell’ordine di Changed, considerare “niente più Changed” come completamento, consolarsi solo con InternalBufferSize, vedere Error e non riprendersi mai: tutti progetti da evitare. Invece, raggruppa le notifiche in richieste di nuova scansione, esegui nuove scansioni complete all’avvio / overflow / riconnessione, assumi la proprietà tramite la ridenominazione delle richieste e assorbi i duplicati e le nuove scansioni con idempotenza.

In altre parole, il trucco con FileSystemWatcher è non equiparare mai “l’aver ricevuto un evento” con “l’autorizzazione all’elaborazione”. La semplice separazione di questi due riduce notevolmente il tipo di codice di monitoraggio che si interrompe solo una volta ogni tanto.

8. Riferimenti

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Perché FileSystemWatcher perde gli eventi?
FileSystemWatcher ha un buffer interno e quando le modifiche si concentrano in una breve finestra, si verifica un overflow e le singole notifiche vengono eliminate. Il buffer predefinito è 8192 byte e non può superare 64 KB e utilizza memoria non paginata, quindi ingrandirlo non è una vera soluzione. Una volta che si verifica un overflow, la completezza dell'intero flusso di eventi diventa sospetta, quindi la risposta giusta è una nuova scansione completa della directory anziché fidarsi della cronologia per evento. Anche i file posizionati prima dell'avvio dell'app non vengono mai segnalati, motivo per cui è essenziale una scansione all'avvio.
Posso considerare l'evento Created come un segnale che il file è pronto per la lettura?
No. `Created` si attiva nel momento in cui viene creato il file, ovvero durante le copie e i trasferimenti mentre il contenuto è ancora in fase di scrittura, con uno o più eventi `Changed` successivi. La lettura a quel punto produce righe mancanti, file JSON corrotti o file ZIP corrotti. Il completamento dovrebbe essere reso esplicito dal lato del mittente tramite temp -> chiudi -> rinomina / sostituisci, o con un file o un manifest completato, piuttosto che dedotto dagli eventi.
Perché FileSystemWatcher attiva la modifica più volte per un salvataggio?
Non è garantito che Changed venga attivato esattamente una volta: anche le operazioni ordinarie come lo spostamento o il salvataggio di un file possono essere visualizzate come più eventi, una ridenominazione può produrre un evento Changed e il software antivirus o gli indicizzatori che toccano il file ne aggiungono altri. Anche l'ordine non è garantito e RenamedEventArgs.Name può anche essere nullo. Quindi non fidarti del conteggio o dell'ordine degli eventi; riunisci tutte le notifiche in un'unica richiesta di nuova scansione "vai a vedere" e assorbi i duplicati con idempotenza.
Qual è la struttura consigliata per l'elaborazione dei file con FileSystemWatcher?
Mantieni i gestori di eventi leggeri: dovrebbero accodare solo una richiesta di nuova scansione, con un singolo lavoratore che esegue la scansione effettiva della directory dopo aver unito i burst per circa 100-300 millisecondi. Eseguire una nuova scansione completa all'avvio, in caso di evento Errore e dopo aver ricreato l'osservatore. Con più lavoratori, assumi la proprietà rinominando atomicamente un file o un bundle dall'ingresso in una directory di elaborazione per lavoratore prima della lettura e registra una chiave di idempotenza in modo che la rielaborazione dello stesso input non conteggi mai due volte i risultati.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog