2009-03-31 12 views
36

Si è verificato un problema con FileSystemWatcher quando più file vengono inseriti nella directory controllata. Voglio analizzare il file non appena viene inserito nella directory. In genere, il primo file analizza correttamente, ma l'aggiunta di un secondo file alla directory provoca un problema di accesso. Occasionalmente, il primo file non viene nemmeno analizzato. C'è solo un'applicazione in esecuzione e guardando questa directory. Alla fine, questo processo verrà eseguito su più macchine e guarderanno una directory condivisa, ma solo un server può analizzare ogni file mentre i dati vengono importati in un database e non ci sono chiavi primarie.Errore di accesso ai file con FileSystemWatcher quando più file vengono aggiunti a una directory

ecco il codice FileSystemWatcher:

public void Run() { 
    FileSystemWatcher watcher = new FileSystemWatcher("C:\\temp"); 
    watcher.NotifyFilter = NotifyFilters.FileName; 
    watcher.Filter = "*.txt"; 

    watcher.Created += new FileSystemEventHandler(OnChanged); 

    watcher.EnableRaisingEvents = true; 
    System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite); 
} 

Poi il metodo che analizza il file:

private void OnChanged(object source, FileSystemEventArgs e) { 
    string line = null; 

    try { 
    using (FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) { 
     using (StreamReader sr = new StreamReader(fs)) { 
     while (sr.EndOfStream == false) { 
      line = sr.ReadLine(); 
      //parse the line and insert into the database 
     } 
     } 
    } 
    } 
    catch (IOException ioe) { 
    Console.WriteLine("OnChanged: Caught Exception reading file [{0}]", ioe.ToString()); 
    } 

Quando si sposta il secondo file, si sta recuperando

System.IO .IOException: il processo non può accedere al file 'C: \ Temp \ TestFile.txt' perché è utilizzato da un altro processo.

Mi piacerebbe vedere questo errore se era in esecuzione su più macchine, ma per ora funziona solo su un server. Non ci dovrebbe essere un altro processo che utilizza questo file: li ho creati e li copio nella directory quando l'applicazione è in esecuzione.

È questo il modo corretto di configurare FileSystemWatcher? Come posso vedere che cosa ha il blocco su questo file? Perché non analizza entrambi i file? Devo chiudere FileStream? Voglio mantenere l'opzione FileShare.None perché voglio solo che un server analizzi il file - il server che ottiene il file lo analizza per primo.

risposta

50

Un tipico problema di questo approccio è che il file viene ancora copiato mentre l'evento viene attivato. Ovviamente, si otterrà un'eccezione perché il file è bloccato durante la copia. Un'eccezione è particolarmente probabile su file di grandi dimensioni.

Come soluzione temporanea è possibile copiare il file e quindi rinominarlo e ascoltare l'evento di ridenominazione.

Oppure un'altra opzione sarebbe avere un ciclo while che verifica se il file può essere aperto con accesso in scrittura. Se è possibile, saprai che la copia è stata completata. Codice C# potrebbe assomigliare a questo (in un sistema di produzione che si potrebbe desiderare di avere un numero massimo di tentativi o di timeout al posto di un while(true)):

/// <summary> 
/// Waits until a file can be opened with write permission 
/// </summary> 
public static void WaitReady(string fileName) 
{ 
    while (true) 
    { 
     try 
     { 
      using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) 
      { 
       if (stream != null) 
       { 
        System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName)); 
        break; 
       } 
      } 
     } 
     catch (FileNotFoundException ex) 
     { 
      System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); 
     } 
     catch (IOException ex) 
     { 
      System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); 
     } 
     catch (UnauthorizedAccessException ex) 
     { 
      System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message)); 
     } 
     Thread.Sleep(500); 
    } 
} 

Ancora un altro approccio potrebbe essere quella di inserire un piccolo file trigger nella cartella dopo aver completato la copia. FileSystemWatcher ascolterà solo il file trigger.

+0

Questi file di test sono piccoli - solo poche righe di testo breve, quindi non dovrebbe essere bloccato per troppo tempo durante la copia. Come posso eseguire il ciclo per verificare se il file è pronto per la scrittura? –

+0

Grazie! Mi vergogno di non riuscire a pensare a un ciclo while come soluzione a questo: avevo bisogno di leggere una versione aggiornata di un file di output di un'altra app. Ho ottenuto più eventi per scrittura, quindi ho utilizzato un timer che viene reimpostato ogni volta e attende X secondi prima di attivare il codice che legge il file aggiornato. Ma poi ho perso gli aggiornamenti se l'app ha scritto due volte entro X secondi. – Gishu

+3

+1 Ma dovresti davvero avere un tempo massimo per aspettare in modo che il processo non sia bloccato per sempre –

0

Ho avuto lo stesso problema in DFS. La mia risoluzione è stata acquisita aggiungendo due righe vuote a ciascun file. Quindi il mio codice attende due righe vuote nel file. Quindi ho la certezza di leggere interi dati dal file.

3

Quando si apre il file nel metodo OnChanged, si specifica FileShare.None, che in base a the documentation, causerà altri tentativi di apertura del file in caso di errore mentre è aperto. Poiché tutto quello che tu e il tuo osservatore state facendo è la lettura, provate a utilizzare FileShare.Read.

+0

La vera soluzione senza stupidi hack. –

2

Soluzione semplice sarebbe quella di smaltire il filesystemwatcher una volta ricevuta la notifica. prima di copiare il file, fai in modo che il thread corrente attenda fino a quando non riceve l'evento di smaltimento di filesystemwatcher. quindi è possibile continuare a copiare il file modificato senza problemi di accesso. Ho avuto lo stesso requisito e l'ho fatto esattamente come quello che ho menzionato. ha funzionato.

Esempio di codice:

public void TestWatcher() 
{ 
    using (var fileWatcher = new FileSystemWatcher()) 
    { 

     string path = @"C:\sv"; 
     string file = "pos.csv"; 

     fileWatcher.Path = path; 
     fileWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite; 
     fileWatcher.Filter = file; 

     System.EventHandler onDisposed = (sender,args) => 
     { 
      eve.Set(); 
     }; 

     FileSystemEventHandler onFile = (sender, fileChange) => 
     { 
      fileWatcher.EnableRaisingEvents = false; 
      Thread t = new Thread(new ParameterizedThreadStart(CopyFile)); 
      t.Start(fileChange.FullPath); 
      if (fileWatcher != null) 
      { 
       fileWatcher.Dispose(); 
      } 
      proceed = false; 
     }; 

     fileWatcher.Changed += onFile; 
     fileWatcher.Created += onFile; 
     fileWatcher.Disposed+= onDisposed; 
     fileWatcher.EnableRaisingEvents = true; 

     while (proceed) 
     { 
      if (!proceed) 
      { 
       break; 
      } 
     } 
    } 
} 

public void CopyFile(object sourcePath) 
{ 
    eve.WaitOne(); 
    var destinationFilePath = @"C:\sv\Co"; 
    if (!string.IsNullOrEmpty(destinationFilePath)) 
    { 
     if (!Directory.Exists(destinationFilePath)) 
     { 
      Directory.CreateDirectory(destinationFilePath); 
     } 
     destinationFilePath = Path.Combine(destinationFilePath, "pos.csv"); 
    }   

    File.Copy((string)sourcePath, destinationFilePath); 
} 
1

mi sento un buon esempio di ciò che si vuole è la ConfigureAndWatchHandler in log4net. Utilizzano un timer per attivare l'evento del gestore file. Ritengo che questo finisca per essere un'implementazione più pulita del ciclo while nel post di 0xA3. Per quelli di voi che non vogliono usare dotPeek per esaminare il file Cercherò di darvi un frammento di codice qui in base al codice OP:

private System.Threading.Timer _timer;  

public void Run() { 
    //setup filewatcher 
    _timer = new System.Threading.Timer(new TimerCallback(OnFileChange), (object) null, -1, -1); 
} 

private void OnFileChange(object state) 
{ 
    try 
    { 
    //handle files 
    } 
    catch (Exception ex) 
    { 
     //log exception 
     _timer.Change(500, -1); 
    } 
} 
9

avrei lasciato un commento precedente, ma Non ho ancora abbastanza punti.

La risposta più votate questa domanda ha un blocco di codice che assomigliano a questo:

using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)) 
{ 
    if (stream != null) 
    { 
     System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName)); 
     break; 
    } 
} 

Il problema con l'utilizzo FileShare.ReadWrite impostazione è che richiede l'accesso al file fondamentalmente dicendo "Voglio leggi/scrivi su questo file, ma altri possono anche leggere/scrivere su di esso. " Questo approccio ha fallito nella nostra situazione. Il processo che stava ricevendo il trasferimento remoto non ha bloccato il file, ma stava scrivendo attivamente su di esso. Il nostro codice downstream (SharpZipLib) non funzionava con l'eccezione "file in uso" perché stava tentando di aprire il file con uno FileShare.Read ("Voglio il file per la lettura e lasciare che anche gli altri processi vengano letti"). Poiché il processo in cui il file era aperto stava già scrivendo su di esso, questa richiesta non è riuscita.

Tuttavia, il codice nella risposta sopra è troppo rilassato. Usando FileShare.ReadWrite, si riusciva ad ottenere l'accesso al file (perché chiedeva una restrizione di condivisione che poteva essere onorato), ma la chiamata downstream continuava a fallire.

L'impostazione quota nella chiamata a File.Open dovrebbe essere o FileShare.Read o FileShare.None, e NONFileShare.ReadWrite. incendi

2

FileSystemWatcher watcher.Created evento due volte per ogni creazione singolo file 1CE all'avvio di copia dei file e il 2 ° tempo in cui di copia dei file è terminato. Tutto ciò che devi fare è ignorare il 1 ° evento e processare la seconda volta.

Un semplice esempio di gestore di eventi:

private bool _fileCreated = false; 
private void FileSystemWatcher_FileCreated(object sender, FileSystemEventArgs e) 
{ 
    if (_fileCreated) 
    { 
     ReadFromFile();//just an example method call to access the new file 
    } 

    _fileCreated = !_fileCreated; 
} 
0
public static BitmapSource LoadImageNoLock(string path) 
{ 
    while (true) 
    { 
     try 
     { 
      var memStream = new MemoryStream(File.ReadAllBytes(path)); 
      var img = new BitmapImage(); 
      img.BeginInit(); 
      img.StreamSource = memStream; 
      img.EndInit(); 
      return img; 
      break; 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
     } 
    } 
} 
1

Ho avuto problemi simili. È solo grazie a FileSystemWatcher. Ho appena usato
Thread.Sleep();

E ora funziona bene. Quando il file arriva nella directory chiama suCreato due volte. così una volta quando il file viene copiato e la seconda volta quando la copia è stata completata. Per quello ho usato Thread.Dormire(); Quindi aspetterà prima chiamo ReadFile();

private static void OnCreated(object source, FileSystemEventArgs e) 
    { 
     try 
     { 
      Thread.Sleep(5000); 
      var data = new FileData(); 
      data.ReadFile(e.FullPath);     
     } 
     catch (Exception ex) 
     { 
      WriteLogforError(ex.Message, String.Empty, filepath); 
     } 
    }