2009-09-10 5 views
22

Wie warte ich auf die Datei frei, so dass ss.Save() es mit einem neuen überschreiben kann. Wenn ich das zweimal eng zusammen führe (ish) bekomme ich einen generic GDI+ Fehler.Warten Sie, bis die Datei vom Prozess freigegeben wird

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

möglich Duplikat of [Gibt es eine Möglichkeit zu überprüfen, ob eine Datei verwendet wird?] (http://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-isin- -use) –

+0

Dieser Code hat einen einfachen Fehler mit 'File.Create (fileName)'. Die Antworten fehlen diesem Punkt. Es ist nicht notwendig auf die Schließung zu warten. – usr

Antwort

45

Eine Funktion wie diese wird es tun:

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

es in einer while-Schleife-Stick und Sie etwas haben, das, bis die Datei

+0

Danke! Ich habe das hier hineingeschoben 'var isReady = false; while (! IsReady) { isReady = IsFileReady (Dateiname); } ' und alles scheint gut. –

+61

können Sie auch 'return inputStream.Length> 0;'. Ich mochte diese 'if (condition) Rückkehr wahr nie; else return false; '.. – Default

+6

@Default Ich denke, die Rückkehr wahr/falsch ist besser lesbar –

2

Es gibt keine Funktion, mit der Sie auf einen bestimmten Handle/Dateisystem-Speicherort warten können, um zum Schreiben verfügbar zu sein. Leider können Sie nur den Griff zum Schreiben abfragen.

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

zugänglich ist blockiert können Sie das System warten lassen , bis der Prozess abgeschlossen ist.

Genau so einfach wie diese:

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

8

Wenn Sie Zugang zu überprüfen, bevor ein anderes Verfahren in die Datei schreiben könnte wieder den Zugang zu entreißen, bevor Sie verwalten Ihre Schreib zu tun. Daher würde ich vorschlagen, eine der beiden folgenden:

  1. Wrap, was Sie in einem Wiederholungs Umfang tun möchten, dass andere Fehler nicht verbergen
  2. ein Wrapper-Methode erstellen, die wartet, bis Sie einen Stream erhalten und nutzen diesen Strom

bekommen einen Stream

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."); 
} 

dann verwenden sie es wie folgt aus:

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

retry Umfang

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."); 
} 

und verwenden dann WithRetry (() => File.WriteAllText (Path.Combine (_directory, Name), Inhalt));

+0

Ich habe auch einen Kern für eine Klasse erstellt, die dieses Verhalten umschließt. Denken Sie jedoch daran, dass dies dazu führen kann, dass Ihre Architektur Probleme hat, wenn mehrere Klassen in derselben Datei auf widersprüchliche Weise lesen und schreiben. Am Ende können Sie auf diese Weise Daten verlieren. https://gist.github.com/ddikman/667f309706fdf4f68b9fab2827b1bcca – Almund

+0

Ich weiß nicht, warum dies nicht die akzeptierte Antwort ist. Der Code ist viel sicherer; Aufruf von 'IsFileReady' in einer' while'-Schleife, wie Gordon Thompsons Antwort zeigt, könnte möglicherweise fehlschlagen. Ein anderer Prozess könnte die Datei sperren, wenn die Schleifenbedingung prüft, ob sie verfügbar ist und Ihr Prozess versucht, tatsächlich darauf zuzugreifen. Nur "e.HResult" ist nicht zugänglich, weil es "geschützt" ist. –

+0

Danke für die Unterstützung, obwohl meine vorgeschlagene Lösung im Vergleich ziemlich überladen ist. Ich mag das Aussehen nicht sonderlich, aber da es keine integrierte Unterstützung im Framework gibt, bleibt Ihnen nur eine Auswahl übrig. Ich habe das HResult verwendet, könnte aber zwischen den Framework-Versionen vielleicht anders sein, ich bin mir sicher, dass es eine andere Eigenschaft gibt, die verwendet werden kann, um festzustellen, welchen Fehler die IOException enthält. – Almund

2

Hier ist eine Lösung, die für einige Benutzer übertrieben sein kann. Ich habe eine neue statische Klasse erstellt, die ein Ereignis enthält, das erst ausgelöst wird, wenn die Datei das Kopieren beendet hat.

Der Benutzer registriert Dateien, die er sich ansehen möchte, indem er FileAccessWatcher.RegisterWaitForFileAccess(filePath) aufruft. Wenn die Datei nicht bereits überwacht wird, wird eine neue Aufgabe gestartet, die die Datei wiederholt überprüft, um zu sehen, ob sie geöffnet werden kann. Bei jeder Überprüfung liest es auch die Dateigröße. Wenn die Dateigröße nicht in einer vordefinierten Zeit (in meinem Beispiel 5 Minuten) zunimmt, wird die Schleife beendet.

Wenn die Schleife aus der Datei, auf die zugegriffen werden kann, oder von der Zeitüberschreitung beendet wird, wird das Ereignis FileFinishedCopying ausgelöst.

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

Beispiel Nutzung:

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

die FileFinishedCopyingEvent Griff:

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

Sie könnten eine lock-Anweisung mit einem Dummy-Variable verwenden, und es scheint sehr gut zu funktionieren.

Überprüfen Sie here.

0

Mit @Gordon Thompson ‚s Antwort, müssen Sie eine Schleife erstellen wie dem folgenden Code:

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

Ich fand eine optimierte Art und Weise, dass keine Speicherlecks verursachen:

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