921 votes

Existe-t-il un moyen de vérifier si un fichier est en cours d'utilisation ?

J'écris un programme en C# qui doit accéder de manière répétée à un fichier image. La plupart du temps, cela fonctionne, mais si mon ordinateur est rapide, il essaiera d'accéder au fichier avant qu'il n'ait été sauvegardé dans le système de fichiers et enverra une erreur :

"Fichier utilisé par un autre processus"

J'aimerais trouver un moyen de contourner ce problème, mais toutes mes recherches sur Google ne m'ont permis que de créer des contrôles en utilisant le traitement des exceptions. Cela va à l'encontre de ma religion, et je me demandais donc si quelqu'un avait une meilleure façon de procéder ?

33 votes

Très bien, vous pouvez le tester en examinant tous les handles ouverts sur le système. Cependant, comme Windows est un système d'exploitation multitâche, il est possible que, juste après avoir exécuté le code pour déterminer si le fichier est ouvert et que vous estimiez qu'il ne l'est pas, un code de processus commence à utiliser ce fichier, puis, au moment où vous essayez de l'utiliser, vous recevez une erreur. Mais il n'y a rien de mal à vérifier d'abord ; ne supposez pas qu'il n'est pas utilisé lorsque vous en avez réellement besoin.

4 votes

Mais juste pour ce problème spécifique, je recommanderais de ne pas examiner les poignées de fichiers et de juste essayer un nombre prédéfini de fois, disons 3-5 avant d'échouer.

0 votes

Comment ce fichier image est-il généré ? Pouvez-vous arrêter/se mettre en veille/pause votre programme jusqu'à ce que la génération soit terminée ? C'est de loin une meilleure façon de gérer la situation. Sinon, je ne pense pas que vous puissiez éviter d'utiliser le traitement des exceptions.

606voto

ChrisW Points 3266

Mise à jour de la NOTE sur cette solution : Vérification avec FileAccess.ReadWrite échouera pour les fichiers en lecture seule, la solution a donc été modifiée pour vérifier avec FileAccess.Read .

ORIGINAL : J'utilise ce code depuis plusieurs années et je n'ai eu aucun problème avec lui.

Je comprends votre hésitation à utiliser les exceptions, mais vous ne pouvez pas les éviter tout le temps :

protected virtual bool IsFileLocked(FileInfo file)
{
    try
    {
        using(FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
        {
            stream.Close();
        }
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }

    //file is not locked
    return false;
}

66 votes

Il s'agit d'une excellente solution, mais j'ai un commentaire à faire : il est préférable de ne pas ouvrir le fichier avec le mode d'accès FileAccess.Read, car ReadWrite échouera toujours si le fichier est en lecture seule.

9 votes

Bonne réponse, mais le problème est que le fichier peut ne pas exister, ce qui entraînera une exception FileNotFound - c'est une IOException. Le bloc de capture de l'IOException l'attrapera et le résultat de la méthode l'interprétera comme un fichier verrouillé, alors qu'en fait le fichier n'existe pas - cela pourrait conduire à des bogues difficiles à diagnostiquer. Une question délicate du point de vue de l'API - peut-être que l'API devrait être GetFileState qui renvoie une énumération : Locked, NotFound, KnockYourselfOut. :) Bien sûr, une UnauthorizedAccessException pourrait être levée, ce qui n'est pas une IOException...

232 votes

-1. C'est une mauvaise réponse, car le fichier pourrait être verrouillé par un autre thread/processus après qu'il ait été fermé par IsFileLocked, et avant que votre thread ait la possibilité de l'ouvrir.

594voto

Spence Points 15057

Vous pouvez souffrir d'une condition de course de threads sur ce point, et il existe des exemples documentés de cette utilisation comme une vulnérabilité de sécurité. Si vous vérifiez que le fichier est disponible, mais que vous essayez ensuite de l'utiliser, vous risquez d'échouer à ce moment-là, ce qu'un utilisateur malveillant pourrait utiliser pour forcer et exploiter votre code.

Votre meilleure chance est un try catch / finally qui essaie d'obtenir le handle du fichier.

try
{
   using (Stream stream = new FileStream("MyFilename.txt", FileMode.Open))
   {
        // File/Stream manipulating code here
   }
} catch {
  //check here why it failed and ask user to retry if the file is in use.
}

140 votes

+1. Il n'existe pas de moyen sûr à 100 % pour "savoir si un fichier est en cours d'utilisation", car quelques millisecondes après avoir effectué la vérification, le fichier peut ne plus être en cours d'utilisation, ou vice versa. Au lieu de cela, vous ouvrez simplement le fichier et l'utilisez s'il n'y a pas d'exception.

10 votes

Dommage que .NET ne supporte pas le CAS. Quelque chose comme TryOpenFile(Ref FileHandle) qui renvoie le succès/l'échec. Il devrait toujours y avoir une solution de rechange pour ne pas dépendre uniquement de la gestion des exceptions. Je me demande comment Microsoft Office le fait.

3 votes

Ce qu'il faut comprendre ici, c'est que cette API utilise simplement l'API Windows pour obtenir un handle de fichier. En tant que telle, elle doit traduire le code d'erreur reçu de l'API C et le transformer en une exception à lancer. Nous disposons d'un système de gestion des exceptions dans .Net, alors pourquoi ne pas l'utiliser ? De cette façon, vous pouvez écrire un chemin direct propre dans votre code et laisser la gestion des erreurs dans un chemin de code séparé.

97voto

Jeremy Thompson Points 14428

Utilisez cette option pour vérifier si un fichier est verrouillé :

using System.IO;
using System.Runtime.InteropServices;
internal static class Helper
{
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;

private static bool IsFileLocked(Exception exception)
{
    int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
    return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION;
}

internal static bool CanReadFile(string filePath)
{
    //Try-Catch so we dont crash the program and can check the exception
    try {
        //The "using" is important because FileStream implements IDisposable and
        //"using" will avoid a heap exhaustion situation when too many handles  
        //are left undisposed.
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
            if (fileStream != null) fileStream.Close();  //This line is me being overly cautious, fileStream will never be null unless an exception occurs... and I know the "using" does it but its helpful to be explicit - especially when we encounter errors - at least for me anyway!
        }
    }
    catch (IOException ex) {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex)) {
            // do something, eg File.Copy or present the user with a MsgBox - I do not recommend Killing the process that is locking the file
            return false;
        }
    }
    finally
    { }
    return true;
}
}

Pour des raisons de performance, je vous recommande de lire le contenu du fichier dans la même opération. Voici quelques exemples :

public static byte[] ReadFileBytes(string filePath)
{
    byte[] buffer = null;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
                sum += count;  // sum is a buffer offset for next reading

            fileStream.Close(); //This is not needed, just me being paranoid and explicitly releasing resources ASAP
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }
    return buffer;
}

public static string ReadFileTextWithEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            //Depending on the encoding you wish to use - I'll leave that up to you
            fileContents = System.Text.Encoding.Default.GetString(buffer);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    { }     
    return fileContents;
}

public static string ReadFileTextNoEncoding(string filePath)
{
    string fileContents = string.Empty;
    byte[] buffer;
    try
    {
        using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            int length = (int)fileStream.Length;  // get file length
            buffer = new byte[length];            // create buffer
            int count;                            // actual number of bytes read
            int sum = 0;                          // total number of bytes read

            // read until Read method returns 0 (end of the stream has been reached)
            while ((count = fileStream.Read(buffer, sum, length - sum)) > 0) 
            {
                sum += count;  // sum is a buffer offset for next reading
            }

            fileStream.Close(); //Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

            char[] chars = new char[buffer.Length / sizeof(char) + 1];
            System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);
            fileContents = new string(chars);
        }
    }
    catch (IOException ex)
    {
        //THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
        if (IsFileLocked(ex))
        {
            // do something? 
        }
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }

    return fileContents;
}

Essayez vous-même :

byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");

10 votes

Je voterais plus haut s'il n'y avait pas autant de "chiffres magiques" là-dedans. fr.wikipedia.org/wiki/Magic_number_(programmation)

3 votes

Je faisais référence aux comparaisons du code d'erreur, pas aux décalages de bits, mais maintenant que vous le dites...

1 votes

Votre Catch devrait être sur IOException au lieu de sur des Exception et ensuite un test sur le type.

13voto

Frank Schwieterman Points 13519

Voici une version powershell de la réponse acceptée.

function IsFileLocked($filename) {

    $result = $false

    $fileinfo = [System.IO.FileInfo] (gi $filename).fullname

    try {
        $stream = $fileInfo.Open([System.IO.FileMode]"Open",[System.IO.FileAccess]"ReadWrite",[System.IO.FileShare]"None")
        $stream.Dispose()
    } catch [System.IO.IOException] {
        $result = $true
    }

    $result
}

7voto

Karl Johan Points 2068

Vous pourriez peut-être utiliser un FileSystemWatcher et surveillez l'événement Changé.

Je ne l'ai pas utilisé moi-même, mais ça peut valoir le coup d'essayer. Si le filesystemwatcher s'avère être un peu lourd pour ce cas, je choisirais la boucle try/catch/sleep.

4 votes

L'utilisation d'un FileSystemWatcher n'est pas utile, car les événements Created et Changed se déclenchent au début de la création ou de la modification d'un fichier. Même les petits fichiers ont besoin de plus de temps pour être écrits et fermés par le système d'exploitation que l'application .NET n'en a besoin pour exécuter le Callback du FileSystemEventHandler. C'est tellement triste, mais il n'y a pas d'autre option que d'estimer le temps d'attente avant d'accéder au fichier ou de se heurter à des boucles d'exception...

1 votes

FileSystemWatcher ne gère pas très bien les nombreux changements simultanés, alors soyez prudent.

1 votes

Au fait, avez-vous remarqué en déboguant et en regardant les fils de discussion que MS appelle son propre FSW "FileSystemWather" ? Qu'est-ce qu'un wather de toute façon ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X