2011-01-13 15 views
25

Ho un programma eseguito come servizio di Windows; elabora i file in una cartella specifica. Poiché si tratta di un servizio, monitora costantemente una cartella per i nuovi file che sono stati aggiunti. Parte del lavoro del programma consiste nell'eseguire confronti di file nella cartella di destinazione e contrassegnare file non corrispondenti. Quello che mi piacerebbe fare è essere in grado di rilevare se un'operazione di copia è in corso e quando è stata completata, in questo modo un file non viene contrassegnato prematuramente se il file corrispondente non è stato ancora copiato nella cartella di destinazione.C# - In attesa di un'operazione di copia da completare

Quello che stavo pensando di fare era usare FileSystemWatcher per guardare la cartella di destinazione e vedere se si sta verificando un'operazione di copia. Se c'è, metto in pausa il thread principale del mio programma fino al completamento dell'operazione di copia, quindi procedo ad eseguire l'operazione sulla cartella come normale. Volevo solo avere un'idea di questo approccio e vedere se è valido; se qualcun altro ha altri approcci univoci a questo problema, sarebbe molto apprezzato.

UPDATE:

Grazie a tutti per i vostri suggerimenti

UPDATE 2:

Mi scuso per la confusione, quando dico directory di destinazione, voglio dire la cartella di origine che contiene tutti i file che voglio processare. Una parte della funzione del mio programma è copiare la struttura di directory della directory di origine in una directory di destinazione e copiare tutti i file validi nella directory di destinazione, preservando la struttura di directory della directory di origine originale, ovvero un utente può copiare cartelle contenenti file alla directory di origine. Voglio evitare errori assicurando che se un nuovo set di cartelle contenente più sottocartelle e file venga copiato nella directory di origine per l'elaborazione, il mio programma non inizierà a funzionare nella directory di destinazione fino al completamento del processo di copia.

+0

+1. Questa è un'ottima domanda. Devo ancora trovare un approccio che non sembri un trucco. – David

+1

Questa domanda è simile e ha alcune buone risposte: http://stackoverflow.com/questions/30074/monitoring-files-how-to-know-when-a-file-is-completo – mfdoran

risposta

2

Quello che stai cercando è un tipico scenario producer/consumer. Quello che devi fare è delineato nella sezione 'Producer/consumer queue' su questo page. Questo ti permetterà di usare il multi threading (magari un backgroundworker) per copiare i file in modo da non bloccare il thread del servizio principale dall'ascolto di eventi di sistema & puoi eseguire compiti più significativi lì - come verificare i nuovi file & aggiornando la coda . Quindi on main thread do check for new files su background threads perform the actual coping task. Dall'esperienza personale (hanno implementato queste attività) non c'è troppa guadagno in termini di prestazioni da questo approccio a meno che non si esegua su più macchine CPU ma il processo è molto pulito & liscio + il codice è separato logicamente bene.

In breve, quello che devi fare è avere un oggetto simile al seguente:

public class File 
{ 
    public string FullPath {get; internal set;} 
    public bool CopyInProgress {get; set;} // property to make sure 
    // .. other properties if desired 
} 

Poi seguendo il tutorial postato sopra questione un blocco sul oggetto File & coda per aggiornarlo & copiarlo. Utilizzando questo approccio è possibile utilizzare questo type approaches invece di monitorare costantemente il completamento della copia di file. Il punto importante da comprendere qui è che il tuo servizio ha solo un'istanza di oggetto File per file fisico reale - assicurati di (1) bloccare la coda quando aggiungi & (2) per bloccare l'oggetto File durante l'inizializzazione di un aggiornamento .

EDIT: Sopra in cui dico "Non c'è troppo guadagno di prestazioni da questo approccio meno" Mi RIFERIME a se fate questo approccio in un singolo thread confrontare @ Jason di aver suggerito questo approccio deve essere notevolmente più veloce a causa @ La soluzione di Jason che esegue operazioni di I/O molto costose che falliranno nella maggior parte dei casi. Questo non l'ho ancora testato, ma sono sicuro che il mio approccio non richiede operazioni IO aperte (solo una volta), stream (solo una volta) & file chiuso (solo una volta). L'approccio @Jason suggerisce più operazioni aperte, aperte, aperte e aperte che, a differenza di , falliscono tutte eccetto l'ultimo.

+0

Questo è ciò che voglio realizzare, devo impedire al mio programma di fare qualsiasi cosa con i file/le cartelle nella directory di origine mentre una copia è in progresso. – kingrichard2005

10

Sì, utilizzare un FileSystemWatcher ma, invece di guardare per l'evento creato, guardare per l'evento modificato. Dopo ogni trigger, prova ad aprire il file. Qualcosa del genere:

var watcher = new FileSystemWatcher(path, filter); 
watcher.Changed += (sender, e) => { 
    FileStream file = null; 
    try { 
     Thread.Sleep(100); // hack for timing issues 
     file = File.Open(
      e.FullPath, 
      FileMode.Open, 
      FileAccess.Read, 
      FileShare.Read 
     ); 
    } 
    catch(IOException) { 
     // we couldn't open the file 
     // this is probably because the copy operation is not done 
     // just swallow the exception 
     return; 
    } 

    // now we have a handle to the file 
}; 

Questo è il meglio che si possa fare, sfortunatamente. Non esiste un modo pulito per sapere che il file è pronto per l'uso.

+0

Questo è praticamente come sto l'ho gestito anche tu. Mi è sempre sembrato "Hack-ish", ma +1 perché non ho niente di meglio. – David

+1

@ivo s: Inefficiente? Costoso? Che importa? Sta accadendo su un thread in background ed è così incredibilmente improbabile che possa essere un collo di bottiglia. Ugh. Inoltre, stai supponendo che abbia il controllo sul processo di copia. Si noti che questo non era originariamente nella sua domanda. – jason

+2

Se ti senti avventuroso, puoi evitare il sovraccarico di dover rilevare le eccezioni utilizzando una chiamata p/invoke per provare ad aprire il file invece della chiamata del wrapper .NET. La chiamata WIN32 sottostante restituisce semplicemente un handle nullo per gli errori (è necessario eseguire il marshalling della chiamata utilizzando SafeFileHandle di .Net per una corretta gestione). Quando la chiamata ha esito positivo, puoi creare un oggetto FileStream da SafeFileHandle. Scrivi il tuo wrapper per la chiamata p/invoke per restituire un FileStream (o null in caso di errore) ed evitare la necessità del try/catch. –

2

Un approccio è tentare di aprire il file e vedere se si verifica un errore. Il file sarà bloccato se è stato copiato. Ciò aprirà il file in modalità condivisa in modo che sarà in conflitto con un blocco di scrittura già aperta sul file:

using(System.IO.File.Open("file", FileMode.Open,FileAccess.Read, FileShare.Read)) {} 

Un altro è quello di verificare la dimensione del file. Cambierà nel tempo se il file viene copiato.

È anche possibile ottenere un elenco di tutte le applicazioni che hanno aperto un determinato file, ma non conosco l'API per questo.

1

So che questa è una domanda vecchia, ma ecco una risposta che ho fatto dopo aver cercato una risposta a questo problema. Questo doveva essere ottimizzato molto per rimuovere parte della proprietà da quello su cui stavo lavorando, quindi questo potrebbe non essere compilato direttamente, ma ti darà un'idea. Questo funziona perfettamente per me:



void BlockingFileCopySync(FileInfo original, FileInfo copyPath) 
{ 
    bool ready = false; 

    FileSystemWatcher watcher = new FileSystemWatcher(); 
    watcher.NotifyFilter = NotifyFilters.LastWrite; 
    watcher.Path = copyPath.Directory.FullName; 
    watcher.Filter = "*" + copyPath.Extension; 
    watcher.EnableRaisingEvents = true; 

    bool fileReady = false; 
    bool firsttime = true; 
    DateTime previousLastWriteTime = new DateTime(); 

    // modify this as you think you need to... 
    int waitTimeMs = 100; 

    watcher.Changed += (sender, e) => 
    { 
     // Get the time the file was modified 
     // Check it again in 100 ms 
     // When it has gone a while without modification, it's done. 
     while (!fileReady) 
     { 
      // We need to initialize for the "first time", 
      // ie. when the file was just created. 
      // (Really, this could probably be initialized off the 
      // time of the copy now that I'm thinking of it.) 
      if (firsttime) 
      { 
       previousLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 
       firsttime = false; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 

      DateTime currentLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 

      bool fileModified = (currentLastWriteTime != previousLastWriteTime); 

      if (fileModified) 
      { 
       previousLastWriteTime = currentLastWriteTime; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 
      else 
      { 
       fileReady = true; 
       break; 
      } 
     } 
    }; 

    System.IO.File.Copy(original.FullName, copyPath.FullName, true); 

    // This guy here chills out until the filesystemwatcher 
    // tells him the file isn't being writen to anymore. 
    while (!fileReady) 
    { 
     System.Threading.Thread.Sleep(waitTimeMs); 
    } 
} 

+0

Purtroppo questa soluzione non funziona per me. GetLastWriteTime non viene aggiornato durante la copia del file. Sto usando Windows 7, forse il comportamento è diverso su Windows Server, non sono sicuro. – KyleLib

+0

@KyleLib - Davvero? Interessante, stavo usando Windows 7 pure. Curioso. Non sono sicuro di cosa farne. Potrei dover rompere questo e rovinare un po '. – trycatch