Question Est-il possible de vérifier si un fichier est en cours d'utilisation?


J'écris un programme en C # qui doit accéder à plusieurs reprises à un fichier image. La plupart du temps cela fonctionne, mais si mon ordinateur fonctionne rapidement, il va essayer d'accéder au fichier avant qu'il ne soit sauvegardé dans le système de fichiers et de lancer une erreur: "Fichier en cours d'utilisation par un autre processus".

Je voudrais trouver un moyen de contourner cela, mais tous mes googlings ont seulement permis de créer des contrôles en utilisant la gestion des exceptions. C'est contre ma religion, alors je me demandais si quelqu'un avait une meilleure façon de le faire?


693
2018-05-18 06:37


origine


Réponses:


NOTE mise à jour sur cette solution: Vérification avec FileAccess.ReadWrite va échouer pour les fichiers en lecture seule afin que la solution a été modifiée pour vérifier avec FileAccess.Read. Alors que cette solution fonctionne parce que vous essayez de vérifier avec FileAccess.Read échouera si le fichier a un verrou Write ou Read, cependant, cette solution ne fonctionnera pas si le fichier n'a pas de verrou Write ou Read, c'est à dire qu'il a été ouvert (pour lire ou écrire) avec FileShare. Lire ou FileShare.Write accès.

ORIGINAL: J'ai utilisé ce code ces dernières années et je n'ai eu aucun problème avec ça.

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

protected virtual bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
    }
    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;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}

445
2018-06-02 01:20



Vous pouvez souffrir d'une condition de concurrence de threads sur ce qui est des exemples documentés de cela étant utilisé comme une faille de sécurité. Si vous vérifiez que le fichier est disponible, mais essayez ensuite de l'utiliser, vous pouvez lancer à ce moment-là, ce qu'un utilisateur malveillant pourrait utiliser pour forcer et exploiter dans votre code.

Votre meilleur pari est un try catch / finally qui essaye d'obtenir le handle de 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.
}

514
2018-05-18 06:57



Utilisez ceci 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 performances, 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-le 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");

79
2018-06-16 02:31



Peut-être pourriez-vous utiliser un FileSystemWatcher et surveillez l'événement Changed.

Je ne m'en suis pas servi moi-même, mais ça vaut peut-être le coup. Si le système de fichiers s'avère être un peu lourd dans ce cas, j'opterais pour la boucle try / catch / sleep.


6
2018-05-18 06:52



le seul moyen que je connaisse est d'utiliser l'API de verrouillage Win32 exclusive qui n'est pas trop rapide, mais des exemples existent.

La plupart des gens, pour une solution simple à cela, simplement essayer / attraper / dormir des boucles.


4
2018-05-18 06:42



static bool FileInUse(string path)
    {
        try
        {
            using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
            {
                fs.CanWrite
            }
            return false;
        }
        catch (IOException ex)
        {
            return true;
        }
    }

string filePath = "C:\\Documents And Settings\\yourfilename";
bool isFileInUse;

isFileInUse = FileInUse(filePath);

// Then you can do some checking
if (isFileInUse)
   Console.WriteLine("File is in use");
else
   Console.WriteLine("File is not in use");

J'espère que cela t'aides!


4
2017-08-22 16:01



Utilisez simplement l'exception comme prévu. Acceptez que le fichier est en cours d'utilisation et réessayez, à plusieurs reprises jusqu'à ce que votre action soit terminée. C'est aussi le plus efficace car vous ne perdez aucun cycle à vérifier l'état avant d'agir.

Utilisez la fonction ci-dessous, par exemple

TimeoutFileAction(() => { System.IO.File.etc...; return null; } );

Méthode réutilisable qui expire après 2 secondes

private T TimeoutFileAction<T>(Func<T> func)
{
    var started = DateTime.UtcNow;
    while ((DateTime.UtcNow - started).TotalMilliseconds < 2000)
    {
        try
        {
            return func();                    
        }
        catch (System.IO.IOException exception)
        {
            //ignore, or log somewhere if you want to
        }
    }
    return default(T);
}

4
2018-05-26 15:29



Vous pouvez renvoyer une tâche qui vous donne un flux dès qu'il devient disponible. C'est une solution simplifiée, mais c'est un bon point de départ. C'est thread safe.

private async Task<Stream> GetStreamAsync()
{
    try
    {
        return new FileStream("sample.mp3", FileMode.Open, FileAccess.Write);
    }
    catch (IOException)
    {
        await Task.Delay(TimeSpan.FromSeconds(1));
        return await GetStreamAsync();
    }
}

Vous pouvez utiliser ce flux comme d'habitude:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}

4
2017-10-20 12:33



Les réponses acceptées ci-dessus souffrent d'un problème où si le fichier a été ouvert pour l'écriture avec un mode FileShare.Read ou si le fichier a un attribut Lecture seule, le code ne fonctionnera pas. Cette solution modifiée fonctionne le plus sûrement, avec deux choses à garder à l'esprit (aussi vrai pour la solution acceptée):

  1. Cela ne fonctionnera pas pour les fichiers qui ont été ouverts avec un mode de partage en écriture
  2. Cela ne prend pas en compte les problèmes de threads, vous devrez donc le verrouiller ou gérer les problèmes de thread séparément.

Gardant ce qui précède à l'esprit, cela vérifie si le fichier est soit verrouillé pour l'écriture ou verrouillé pour empêcher la lecture:

public static bool FileLocked(string FileName)
{
    FileStream fs = null;

    try
    {
        // NOTE: This doesn't handle situations where file is opened for writing by another process but put into write shared mode, it will not throw an exception and won't show it as write locked
        fs = File.Open(FileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); // If we can't open file for reading and writing then it's locked by another process for writing
    }
    catch (UnauthorizedAccessException) // https://msdn.microsoft.com/en-us/library/y973b725(v=vs.110).aspx
    {
        // This is because the file is Read-Only and we tried to open in ReadWrite mode, now try to open in Read only mode
        try
        {
            fs = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None);
        }
        catch (Exception)
        {
            return true; // This file has been locked, we can't even open it to read
        }
    }
    catch (Exception)
    {
        return true; // This file has been locked
    }
    finally
    {
        if (fs != null)
            fs.Close();
    }
    return false;
}

2
2017-10-15 13:34



Voici un code qui, pour autant que je puisse le dire, fait la même chose que la réponse acceptée mais avec moins de code:

    public static bool IsFileLocked(string file)
    {
        try
        {
            using (var stream = File.OpenRead(file))
                return false;
        }
        catch (IOException)
        {
            return true;
        }        
    }

Cependant je pense qu'il est plus robuste de le faire de la manière suivante:

    public static void TryToDoWithFileStream(string file, Action<FileStream> action, 
        int count, int msecTimeOut)
    {
        FileStream stream = null;
        for (var i = 0; i < count; ++i)
        {
            try
            {
                stream = File.OpenRead(file);
                break;
            }
            catch (IOException)
            {
                Thread.Sleep(msecTimeOut);
            }
        }
        action(stream);
    }

2
2017-08-22 20:41



Dans mon expérience, vous voulez généralement faire cela, puis «protéger» vos fichiers pour faire quelque chose de fantaisie et ensuite utiliser les fichiers «protégés». Si vous n'avez qu'un seul fichier à utiliser comme ceci, vous pouvez utiliser l'astuce expliquée dans la réponse de Jeremy Thompson. Cependant, si vous essayez de faire cela sur beaucoup de fichiers (par exemple, lorsque vous écrivez un programme d'installation), vous en souffrez beaucoup.

Une façon très élégante de résoudre ce problème est d'utiliser le fait que votre système de fichiers ne vous permettra pas de changer un nom de dossier si l'un des fichiers est utilisé. Conservez le dossier dans le même système de fichiers et cela fonctionnera comme un charme.

Notez que vous devez être conscient des moyens évidents que cela peut être exploité. Après tout, les fichiers ne seront pas verrouillés. Sachez également qu'il existe d'autres raisons qui peuvent entraîner votre Move opération à échouer. Évidemment, la gestion correcte des erreurs (MSDN) peut aider ici.

var originalFolder = @"c:\myHugeCollectionOfFiles"; // your folder name here
var someFolder = Path.Combine(originalFolder, "..", Guid.NewGuid().ToString("N"));

try
{
    Directory.Move(originalFolder, someFolder);

    // Use files
}
catch // TODO: proper exception handling
{
    // Inform user, take action
}
finally
{
    Directory.Move(someFolder, originalFolder);
}

Pour les dossiers individuels je m'en tenir à la suggestion de verrouillage posté par Jeremy Thompson.


1
2017-09-02 10:31