2009-09-10 7 views
22

Comment j'attends que le fichier soit libre pour que ss.Save() puisse l'écraser avec un nouveau fichier. Si je cours ceci deux fois près (ish) j'obtiens une erreur generic GDI+.Attendre que le fichier soit libéré par le processus

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

double possible de [Existe-t-il un moyen de vérifier si un fichier est en cours d'utilisation?] (http://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in -use) –

+0

Ce code a un bug simple avec 'File.Create (fileName)'. Les réponses manquent ce point. Il n'est pas nécessaire d'attendre la fermeture. – usr

Répondre

45

Une fonction comme celui-ci fera:

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

bâton dans une boucle while et vous avez quelque chose qui bloque jusqu'à ce que le fichier est accessible

+0

Merci! J'ai jeté ça là 'var isReady = false; while (! IsReady) { isReady = IsFileReady (nomFichier); } ' et tout semble bien. –

+61

vous pouvez également faire 'return inputStream.Length> 0;'. Je n'ai jamais aimé ces 'si (condition) retourner vrai; else return false; '.. – Default

+6

@Default Je pense que retourner vrai/faux est plus lisible –

2

Il n'y a pas de fonction là-bas qui vous permettra d'attendre sur un emplacement du système poignée/fichier particulier être disponible pour l'écriture. Malheureusement, tout ce que vous pouvez faire est d'interroger la poignée pour l'écriture.

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

Vous pouvez laisser l'attente du système , jusqu'à ce que le processus soit fermé.

Tout aussi simple que cela:

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

8

Si vous cochez l'accès avant d'écrire au fichier un autre processus pourrait arracher l'accès à nouveau avant que vous parvenez à faire votre écriture. À cet effet, je vous suggère l'un des deux éléments suivants:

  1. Wrap ce que vous voulez faire dans un champ de nouvelle tentative qui ne cache pas une autre erreur
  2. Créer une méthode d'emballage qui attend jusqu'à ce que vous pouvez obtenir un cours d'eau et utiliser ce flux

obtenir un flux

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

utiliser alors comme ceci:

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

portée de nouvelle tentative

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

et ensuite utiliser WithRetry (() => File.WriteAllText (Path.Combine (_directory, nom), le contenu));

+0

J'ai également créé un sens pour une classe enveloppant ce comportement. Gardez à l'esprit que cela pourrait signifier que votre architecture a des problèmes si plusieurs classes lisent et écrivent dans le même fichier d'une manière conflictuelle. Vous risquez de perdre des données de cette façon. https://gist.github.com/ddikman/667f309706fdf4f68b9fab2827b1bcca – Almund

+0

Je ne sais pas pourquoi ce n'est pas la réponse acceptée. Le code est beaucoup plus sûr; L'appel de 'IsFileReady' dans une boucle' while', comme le suggère la réponse de Gordon Thompson, pourrait potentiellement échouer. Un autre processus peut verrouiller le fichier entre quand la condition de boucle vérifie si elle est disponible et votre processus tente d'y accéder réellement. La seule chose, 'e.HResult' est inaccessible parce qu'il est' protected'. –

+0

Merci pour le support bien que ma solution suggérée soit assez encombrée en comparaison. Je n'aime pas beaucoup le look mais vu qu'il n'y a pas de support intégré dans le framework, il ne vous reste plus que quelques options. J'utilisais cependant le HResult, peut-être différemment entre les versions du framework, je suis sûr qu'il y a une autre propriété qui peut être utilisée pour détecter l'erreur que l'exception IOException contient. – Almund

2

Voici une solution qui peut être excessive pour certains utilisateurs. J'ai créé une nouvelle classe statique qui a un événement qui n'est déclenché que lorsque le fichier a fini de copier. L'utilisateur enregistre les fichiers qu'il souhaite regarder en appelant le FileAccessWatcher.RegisterWaitForFileAccess(filePath). Si le fichier n'est pas déjà regardé, une nouvelle tâche est démarrée qui vérifie à plusieurs reprises le fichier pour voir s'il peut être ouvert. Chaque fois qu'il vérifie, il lit également la taille du fichier. Si la taille du fichier n'augmente pas dans un temps prédéfini (5 minutes dans mon exemple), la boucle est fermée.

Lorsque la boucle quitte le fichier accessible ou à partir du délai d'attente, l'événement FileFinishedCopying est déclenché.

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

Exemple d'utilisation:

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

Manipulez le FileFinishedCopyingEvent:

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

Vous pouvez utiliser une instruction de verrouillage avec une variable factice, et il semble fonctionner à merveille.

Vérifiez here.

0

En utilisant la réponse de @Gordon Thompson, vous devez créer une boucle comme le code ci-dessous:

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

J'ai trouvé une façon optimisée qui ne provoquent des fuites de mémoire:

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