2009-09-10 45 views
22

Come attendere che il file sia libero in modo che ss.Save() possa sovrascriverlo con uno nuovo. Se eseguo questo due volte insieme (ish) ottengo un errore generic GDI+.Attendere il rilascio del file per processo

///<summary> 
    /// Grabs a screen shot of the App and saves it to the C drive in jpg 
    ///</summary> 
    private static String GetDesktopImage(DevExpress.XtraEditors.XtraForm whichForm) 
    { 
     Rectangle bounds = whichForm.Bounds; 

     // This solves my problem but creates a clutter issue 
     //var timeStamp = DateTime.Now.ToString("ddd-MMM-dd-yyyy-hh-mm-ss"); 
     //var fileName = "C:\\HelpMe" + timeStamp + ".jpg"; 

     var fileName = "C:\\HelpMe.jpg"; 
     File.Create(fileName); 
     using (Bitmap ss = new Bitmap(bounds.Width, bounds.Height)) 
     using (Graphics g = Graphics.FromImage(ss)) 
     { 
      g.CopyFromScreen(whichForm.Location, Point.Empty, bounds.Size); 
      ss.Save(fileName, ImageFormat.Jpeg); 
     } 

     return fileName; 
    } 
+3

possibile duplicato di [C'è un modo per verificare se un file è in uso?] (http://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in -use) –

+0

Questo codice ha un semplice bug con 'File.Create (fileName)'. Le risposte mancano quel punto. Non è necessario attendere la chiusura. – usr

risposta

45

una funzione come questa lo farà:

public static bool IsFileReady(String sFilename) 
    { 
     // If the file can be opened for exclusive access it means that the file 
     // is no longer locked by another process. 
     try 
     { 
      using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      { 
       if (inputStream.Length > 0) 
       { 
        return true; 
       } 
       else 
       { 
        return false; 
       } 

      } 
     } 
     catch (Exception) 
     { 
      return false; 
     } 
    } 

bastone in un ciclo while e hai qualcosa che bloccherà fino a quando il file è accessibile

+0

Grazie! L'ho buttato lì dentro 'var isReady = false; while (! IsReady) { isReady = IsFileReady (fileName); } e tutto sembra bene. –

+61

puoi anche fare 'return inputStream.Length> 0;'. Non mi sono mai piaciuti quei 'if (condition) return true; else restituisce false; '.. – Default

+6

@Default Penso che restituire true/false sia più leggibile –

2

Non esiste una funzione là fuori che vi permetterà di aspettare su una particolare posizione della maniglia/file system ad essere disponibile per la scrittura. Purtroppo, tutto ciò che puoi fare è sondare la maniglia per scrivere.

3
bool isLocked = true; 
while (isLocked) 
try { 
    System.IO.File.Move(filename, filename2); 
    isLocked = false; 
} 
catch { } 
System.IO.File.Move(filename2, filename); 
2

È possibile lasciare che l'attesa del sistema , fino a quando il processo è chiuso.

Altrettanto semplice come questo:

Process.Start("the path of your text file or exe").WaitForExit();

8

Se si controlla l'accesso prima di scrivere al file un altro processo potrebbe strappare di nuovo l'accesso prima di si riesce a fare la tua scrittura. Perciò vorrei suggerire una delle seguenti due:

  1. Wrap ciò che si vuole fare in un ambito di tentativi che non nascondere qualsiasi altro errore
  2. Creare un metodo wrapper che attende fino a quando si può ottenere un ruscello e utilizzare quel flusso

ottenere un flusso

private FileStream GetWriteStream(string path, int timeoutMs) 
{ 
    var time = Stopwatch.StartNew(); 
    while (time.ElapsedMilliseconds < timeoutMs) 
    { 
     try 
     { 
      return new FileStream(path, FileMode.Create, FileAccess.Write); 
     } 
     catch (IOException e) 
     { 
      // access error 
      if (e.HResult != -2147024864) 
       throw; 
     } 
    } 

    throw new TimeoutException($"Failed to get a write handle to {path} within {timeoutMs}ms."); 
} 

quindi utilizzarlo in questo modo:

using (var stream = GetWriteStream("path")) 
{ 
    using (var writer = new StreamWriter(stream)) 
     writer.Write("test"); 
} 

portata retry

private void WithRetry(Action action, int timeoutMs = 1000) 
{ 
    var time = Stopwatch.StartNew(); 
    while(time.ElapsedMilliseconds < timeoutMs) 
    { 
     try 
     { 
      action(); 
      return; 
     } 
     catch (IOException e) 
     { 
      // access error 
      if (e.HResult != -2147024864) 
       throw; 
     } 
    } 
    throw new Exception("Failed perform action within allotted time."); 
} 

e quindi utilizzare WithRetry (() => File.WriteAllText (Path.Combine (_directory, nome), contenuti));

+0

Ho anche creato un elenco per una classe che include questo comportamento. Tenete presente, ovviamente, che ciò potrebbe significare che la vostra architettura presenta problemi se diverse classi leggono e scrivono sullo stesso file in modo conflittuale. Si può finire per perdere dati in questo modo. https://gist.github.com/ddikman/667f309706fdf4f68b9fab2827b1bcca – Almund

+0

Non so perché questa non è la risposta accettata. Il codice è molto più sicuro; chiamare 'IsFileReady' in un ciclo' while', come la risposta di Gordon Thompson suggerisce potrebbe potenzialmente fallire. Un altro processo potrebbe bloccare il file tra quando la condizione del ciclo verifica se è disponibile e il processo tenta di accedervi effettivamente. Unica cosa, 'e.HResult' è inaccessibile perché è' protected'. –

+0

Grazie per il supporto anche se la mia soluzione suggerita è piuttosto disordinata in confronto. Non mi piace molto l'aspetto di esso, tuttavia dal momento che non vi è alcun supporto integrato nel framework, vi rimangono poche opzioni. Tuttavia, stavo usando HResult, potrebbe essere diverso tra le versioni di framework, sono sicuro che ci sono altre proprietà che possono essere utilizzate per rilevare quale errore contiene IOException. – Almund

2

Ecco una soluzione che potrebbe essere eccessiva per alcuni utenti. Ho creato una nuova classe statica che ha un evento che viene attivato solo quando il file termina la copia.

L'utente registra i file che vorrebbero vedere chiamando lo FileAccessWatcher.RegisterWaitForFileAccess(filePath). Se il file non viene ancora visto, viene avviata una nuova attività che controlla ripetutamente il file per vedere se può essere aperto. Ogni volta che controlla, legge anche le dimensioni del file. Se la dimensione del file non aumenta in un tempo predefinito (5 minuti nel mio esempio) il ciclo viene chiuso.

Quando il loop esce dal file accessibile o dal timeout viene attivato l'evento FileFinishedCopying.

public class FileAccessWatcher 
{ 
    // this list keeps track of files being watched 
    private static ConcurrentDictionary<string, FileAccessWatcher> watchedFiles = new ConcurrentDictionary<string, FileAccessWatcher>(); 

    public static void RegisterWaitForFileAccess(string filePath) 
    { 
     // if the file is already being watched, don't do anything 
     if (watchedFiles.ContainsKey(filePath)) 
     { 
      return; 
     } 
     // otherwise, start watching it 
     FileAccessWatcher accessWatcher = new FileAccessWatcher(filePath); 
     watchedFiles[filePath] = accessWatcher; 
     accessWatcher.StartWatching(); 
    } 

    /// <summary> 
    /// Event triggered when the file is finished copying or when the file size has not increased in the last 5 minutes. 
    /// </summary> 
    public static event FileSystemEventHandler FileFinishedCopying; 

    private static readonly TimeSpan MaximumIdleTime = TimeSpan.FromMinutes(5); 

    private readonly FileInfo file; 

    private long lastFileSize = 0; 

    private DateTime timeOfLastFileSizeIncrease = DateTime.Now; 

    private FileAccessWatcher(string filePath) 
    { 
     this.file = new FileInfo(filePath); 
    } 

    private Task StartWatching() 
    { 
     return Task.Factory.StartNew(this.RunLoop); 
    } 

    private void RunLoop() 
    { 
     while (this.IsFileLocked()) 
     { 
      long currentFileSize = this.GetFileSize(); 
      if (currentFileSize > this.lastFileSize) 
      { 
       this.lastFileSize = currentFileSize; 
       this.timeOfLastFileSizeIncrease = DateTime.Now; 
      } 

      // if the file size has not increased for a pre-defined time limit, cancel 
      if (DateTime.Now - this.timeOfLastFileSizeIncrease > MaximumIdleTime) 
      { 
       break; 
      } 
     } 

     this.RemoveFromWatchedFiles(); 
     this.RaiseFileFinishedCopyingEvent(); 
    } 

    private void RemoveFromWatchedFiles() 
    { 
     FileAccessWatcher accessWatcher; 
     watchedFiles.TryRemove(this.file.FullName, out accessWatcher); 
    } 

    private void RaiseFileFinishedCopyingEvent() 
    { 
     FileFinishedCopying?.Invoke(this, 
      new FileSystemEventArgs(WatcherChangeTypes.Changed, this.file.FullName, this.file.Name)); 
    } 

    private long GetFileSize() 
    { 
     return this.file.Length; 
    } 

    private bool IsFileLocked() 
    { 
     try 
     { 
      using (this.file.Open(FileMode.Open)) { } 
     } 
     catch (IOException e) 
     { 
      var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1); 

      return errorCode == 32 || errorCode == 33; 
     } 

     return false; 
    } 
} 

Esempio di utilizzo:

// register the event 
FileAccessWatcher.FileFinishedCopying += FileAccessWatcher_FileFinishedCopying; 

// start monitoring the file (put this inside the OnChanged event handler of the FileSystemWatcher 
FileAccessWatcher.RegisterWaitForFileAccess(fileSystemEventArgs.FullPath); 

Maneggiare il FileFinishedCopyingEvent:

private void FileAccessWatcher_FileFinishedCopying(object sender, FileSystemEventArgs e) 
{ 
    Console.WriteLine("File finished copying: " + e.FullPath); 
} 
0

Si potrebbe utilizzare una dichiarazione di blocco con una variabile dummy, e sembra funzionare molto.

Controllare here.

0

Utilizzando risposta @Gordon Thompson 's, è necessario creare un ciclo come ad esempio il codice qui sotto:

public static bool IsFileReady(string sFilename) 
{ 
    try 
    { 
     using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      return inputStream.Length > 0; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 
} 

while (!IsFileReady(yourFileName)) ; 

ho trovato un modo ottimizzato che non provoca perdite di memoria:

public static bool IsFileReady(this string sFilename) 
{ 
    try 
    { 
     using (FileStream inputStream = File.Open(sFilename, FileMode.Open, FileAccess.Read, FileShare.None)) 
      return inputStream.Length > 0; 
    } 
    catch (Exception) 
    { 
     return false; 
    } 
} 

SpinWait.SpinUntil(yourFileName.IsFileReady); 
Problemi correlati