web-dev-qa-db-fra.com

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 à plusieurs reprises à un fichier image. La plupart du temps, cela fonctionne, mais si mon ordinateur tourne vite, il essaiera d'accéder au fichier avant qu'il ne soit enregistré dans le système de fichiers et émettra une erreur: "Fichier utilisé par un autre processus" .

J'aimerais trouver un moyen de contourner ce problème, mais toutes mes recherches sur Google ont abouti à la création de contrôles en utilisant la gestion des exceptions. C'est contre ma religion, alors je me demandais si quelqu'un avait un meilleur moyen de le faire?

743
Dawsy

NOTE mise à jour sur cette solution : La vérification avec FileAccess.ReadWrite échouera pour les fichiers en lecture seule. La solution a donc été modifiée pour permettre la vérification avec FileAccess.Read. Bien que cette solution fonctionne car essayer de vérifier avec FileAccess.Read échouera si le fichier est verrouillé en écriture ou en lecture, mais cette solution ne fonctionnera pas si le fichier ne comporte pas de verrou en écriture ou en lecture, ouvert (pour la lecture ou l'écriture) avec accès FileShare.Read ou FileShare.Write.

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

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

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

Votre meilleur pari est un essai/enfin qui tente d’obtenir le descripteur 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.
}
524
Spence

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 au cours de 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");
82
Jeremy Thompson

Vous pourriez peut-être utiliser FileSystemWatcher et surveiller l’événement Changed.

Je ne l'ai pas utilisé moi-même, mais cela pourrait valoir le coup. Si le système de fichiers s'avère un peu lourd dans ce cas, je choisirais la boucle try/catch/sleep.

6
Karl Johan

Utilisez simplement l'exception comme prévu. Acceptez que le fichier est en cours d'utilisation et réessayez plusieurs fois jusqu'à ce que votre action soit terminée. C'est aussi le plus efficace car vous ne perdez aucun cycle en vérifiant 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);
}
5
kernowcode

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

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
Ivan Branets
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
Julian

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

La plupart des gens, pour une solution simple, essaient simplement/attrapent/dorment des boucles.

4
Luke Schafer

Vous pouvez utiliser ma bibliothèque pour accéder aux fichiers de plusieurs applications.

Vous pouvez l'installer à partir d'un nuget: Install-Package Xabe.FileLock

Si vous souhaitez plus d’informations à ce sujet, cochez la case https://github.com/tomaszzmuda/Xabe.FileLock

ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
    using(fileLock)
    {
        // file operations here
    }
}

la méthode fileLock.Acquire ne renverra true que si peut verrouiller le fichier exclusif à cet objet . Mais l'application dans laquelle le fichier téléchargé doit également le verrouiller . Si l'objet est inaccessible, la méthode retourne false.

2
Tomasz Żmuda

Les réponses acceptées ci-dessus rencontrent un problème dans lequel si le fichier a été ouvert en é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 de manière très fiable, avec deux choses à garder à l'esprit (comme pour la solution acceptée également):

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

Gardant ce qui précède à l’esprit, ceci vérifie si le fichier est/ verrouillé en écriture ou bloqué 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
rboy

Voici un code qui, à ma connaissance, 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
cdiggins

D'après mon expérience, vous voulez généralement faire cela, puis «protéger» vos fichiers afin de faire quelque chose d'extraordinaire, puis utiliser les fichiers «protégés». Si vous ne voulez utiliser qu'un seul fichier comme celui-ci, vous pouvez utiliser le truc expliqué dans la réponse de Jeremy Thompson. Cependant, si vous tentez de le faire sur de nombreux fichiers (par exemple, lorsque vous écrivez un programme d'installation), vous êtes blessé.

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

Notez que vous devez être conscient des manières évidentes dont cela peut être exploité. Après tout, les fichiers ne seront pas verrouillés. Sachez également qu'il existe d'autres raisons pouvant entraîner l'échec de votre opération Move. Évidemment, le traitement correct 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 fichiers individuels, je me contenterais de la suggestion de verrouillage publiée par Jeremy Thompson.

1
atlaste

En plus de travailler sur trois lignes et juste pour référence: Si vous voulez que les informations complètes - il y a un petit projet sur Microsoft Dev Center:

https://code.msdn.Microsoft.com/windowsapps/How-to-know-the-process-704839f4

De l'introduction:

L'exemple de code C # développé dans .NET Framework 4.0 pourrait aider trouver quel est le processus qui a un verrou sur un fichier . La fonction RmStartSession incluse dans rstrtmgr.dll a été utilisé pour créer une session du gestionnaire de redémarrage et en fonction du retour Il en résulte qu'une nouvelle instance d'objet Win32Exception est créée. Après enregistrement des ressources dans une session Restart Manager via Fonction RmRegisterRescources, la fonction RmGetList est appelée pour vérifier quelles sont les applications utilisent un fichier particulier en énumérant le tableau RM_PROCESS_INFO.

Cela fonctionne en se connectant à la "session de redémarrage du gestionnaire".

Restart Manager utilise la liste des ressources enregistrées avec la session pour déterminer quelles applications et quels services doivent être arrêtés et redémarrés . Les ressources peuvent être identifiées par des noms de fichiers, des noms abrégés de services ou RM_UNIQUE_PROCESS structures décrivant les applications en cours d'exécution.

Cela pourrait être un peu sur-conçu pour vos besoins particuliers ... Mais si c'est ce que vous voulez, allez-y et prenez le vs-projet.

1
Bernhard

Je suis intéressé de voir si cela déclenche des réflexes WTF. J'ai un processus qui crée et lance par la suite un document PDF à partir d'une application console. Cependant, je faisais face à une fragilité: si l'utilisateur devait exécuter le processus plusieurs fois, générant le même fichier sans fermer le fichier précédemment généré, l'application lirait une exception et mourrait. Cela s'est produit assez fréquemment car les noms de fichiers sont basés sur les numéros de devis.

Plutôt que d’échouer de manière aussi ingrate, j’ai décidé de miser sur le contrôle de version des fichiers auto-incrémenté:

private static string WriteFileToDisk(byte[] data, string fileName, int version = 0)
{
    try
    {
        var versionExtension = version > 0 ? $"_{version:000}" : string.Empty;
        var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{fileName}{versionExtension}.pdf");
        using (var writer = new FileStream(filePath, FileMode.Create))
        {
            writer.Write(data, 0, data.Length);
        }
        return filePath;
    }
    catch (IOException)
    {
        return WriteFileToDisk(data, fileName, ++version);
    }
}

Le bloc catch peut probablement faire l'objet d'une plus grande attention afin de m'assurer que les exceptions IOException correctes sont détectées. Je vais probablement aussi vider le stockage de l'application au démarrage, car ces fichiers sont de toute façon temporaires.

Je réalise que cela va au-delà de la simple question de vérifier si le fichier est en cours d'utilisation mais le problème que je cherchais à résoudre à mon arrivée était peut-être utile à quelqu'un d'autre.

0
Vinney Kelly

Est-ce que quelque chose comme cette aide?

var fileWasWrittenSuccessfully = false;
while (fileWasWrittenSuccessfully == false)
{
    try
    {
        lock (new Object())
        {
            using (StreamWriter streamWriter = new StreamWriter(filepath.txt"), true))
            {
                streamWriter.WriteLine("text");
            }
        }

        fileWasWrittenSuccessfully = true;
    }
    catch (Exception)
    {

    }
}
0
Tadej

Une fois, j'ai eu besoin de télécharger des PDF dans une archive de sauvegarde en ligne. Mais la sauvegarde échouerait si le fichier était ouvert par l'utilisateur dans un autre programme (tel que PDF reader). Dans ma hâte, j'ai tenté quelques-unes des principales réponses de ce fil, mais je n'ai pas réussi à les faire fonctionner. Ce qui a fonctionné pour moi, c’est d’essayer de déplacer le fichier PDF vers son propre répertoire. J'ai constaté que cela échouerait si le fichier était ouvert dans un autre programme et si le déplacement aboutissait, aucune opération de restauration ne serait requise, contrairement à ce qui se passerait s'il était déplacé vers un répertoire distinct. Je souhaite publier ma solution de base au cas où elle serait utile pour les cas d'utilisation spécifiques des autres.

string str_path_and_name = str_path + '\\' + str_filename;
FileInfo fInfo = new FileInfo(str_path_and_name);
bool open_elsewhere = false;
try
{
    fInfo.MoveTo(str_path_and_name);
}
catch (Exception ex)
{
    open_elsewhere = true;
}

if (open_elsewhere)
{
    //handle case
}
0