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


846

J'écris un programme en C # qui doit accéder à plusieurs reprises à 1 fichier image. La plupart du temps, cela fonctionne, mais si mon ordinateur fonctionne rapidement, il essaiera d'accéder au fichier avant qu'il ne soit enregistré de nouveau dans le système de fichiers et générera une erreur: "Fichier utilisé par un autre processus" .

Je voudrais trouver un moyen de contourner cela, mais tout mon googling n'a donné lieu à la création de contrôles qu'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?


30
Très bien, vous pouvez le tester en examinant toutes les poignées ouvertes du système. Cependant, étant donné que Windows est un système d'exploitation multitâche, il est possible qu'après avoir exécuté le code pour déterminer si le fichier est ouvert et que vous ne le considérez pas, un code de processus commence à utiliser ce fichier, puis au moment où vous essayez de utilisez-le, vous recevez une erreur. Mais il n'y a rien de mal à vérifier d'abord; ne présumez pas qu'il n'est pas utilisé lorsque vous en avez réellement besoin.
BobbyShaftoe

3
Mais juste pour ce problème spécifique; Je vous recommande de ne pas examiner les descripteurs de fichiers et de simplement essayer un certain nombre de fois prédéfini, par exemple 3-5 avant d'échouer.
BobbyShaftoe

Comment ce fichier image est-il généré? Pouvez-vous arrêter / dormir / mettre en pause votre programme jusqu'à la fin de la génération? C'est de loin une façon supérieure de gérer la situation. Sinon, je ne pense pas que vous puissiez éviter d'utiliser la gestion des exceptions.
Catchwa

Toute utilisation d'exceptions n'est-elle pas une vérification d'une hypothèse en faisant quelque chose de potentiellement dangereux sans exclure délibérément la possibilité d'échec?
jwg

26
Votre philosophie comprend mal les exceptions. La plupart des gens pensent que les exceptions signifient saint-merde-de-doom-quelque chose de mal-die-die-die. Quand exception signifie ... exception. Cela signifie que quelque chose d'exceptionnel s'est produit que vous devez "gérer" (ou rendre compte). Peut-être que vous souhaitez continuer à réessayer pour accéder aux données, peut-être que l'utilisateur doit savoir que vous ne pouvez pas obtenir de connexion. Que faire? Vous gérez l'exception ConnectionFailedException et informez l'utilisateur, alors peut-être qu'il cessera d'essayer après une heure et remarquera que le câble est débranché.
Lee Louviere

Réponses:


543

REMARQUE 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 vérifier avec FileAccess.Read. Bien que cette solution fonctionne car la tentative de vérification FileAccess.Readéchouera si le fichier possède un verrou en écriture ou en lecture, cependant, cette solution ne fonctionnera pas si le fichier n'a pas de verrou en écriture ou en lecture, c'est-à-dire qu'il a été ouvert. (pour la lecture ou l'écriture) avec un 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 lui.

Comprenez votre hésitation à utiliser des 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;
}

60
C'est une excellente solution, mais j'ai un commentaire - vous ne voudrez peut-être pas ouvrir le fichier avec le mode d'accès FileAccess.Read car ReadWrite échouera toujours si le fichier se trouve en lecture seule.
adeel825

220
-1. Il s'agit d'une mauvaise réponse, car le fichier peut être verrouillé par un autre thread / processus après sa fermeture dans IsFileLocked et avant que votre thread n'ait la possibilité de l'ouvrir.
Polyfun

16
Je pense que c'est une excellente réponse. J'utilise ceci comme méthode d'extension á la public static bool IsLocked(this FileInfo file) {/*...*/}.
Manuzor

54
@ChrisW: vous vous demandez peut-être ce qui se passe. Ne vous inquiétez pas. Vous êtes juste soumis à la colère de la communauté Daily WTF: thedailywtf.com/Comments/…
Pierre Lebeaupin

16
@ChrisW Pourquoi est-ce une mauvaise chose. Cette communauté est là pour signaler les bonnes et les mauvaises réponses. Si un tas de professionnels remarquent que c'est une mauvaise chose et se joignent à downvote, alors le site est WAI. Et avant d'être négatif, si vous lisez cet article, ils disent de «voter pour la bonne réponse» et non de voter pour la mauvaise. Voulez-vous qu'ils expliquent également leurs votes positifs dans les commentaires. Merci de m'avoir présenté un autre bon site!
Lee Louviere

570

Vous pouvez souffrir d'une condition de concurrence critique sur les threads sur laquelle il existe des exemples documentés de cette 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 dans votre code.

Votre meilleur pari est un try catch / enfin qui essaie d'obtenir le traitement 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.
}

124
+1. Il n'y a pas de moyen sûr à 100% de "savoir si un fichier est en cours d'utilisation" car, millisecondes après avoir effectué la vérification, le fichier peut ne plus être utilisé, ou vice versa. Au lieu de cela, il vous suffit d'ouvrir le fichier et de l'utiliser s'il n'y a aucune exception.
Sedat Kapanoglu

8
Dommage .NET ne prend pas en charge CAS. Quelque chose comme TryOpenFile (Ref FileHandle) qui renvoie le succès / l'échec. Il devrait toujours y avoir une solution de contournement ne pas se fier uniquement à la gestion des exceptions. Je me demande comment Microsoft Office le fait.
TamusJRoyce

2
L'essentiel à comprendre ici est que cette API utilise simplement l'API Windows pour obtenir un descripteur de fichier. En tant que tels, ils doivent traduire le code d'erreur reçu de l'API C et l'envelopper dans une exception à lever. Nous avons une gestion des exceptions dans .Net, alors pourquoi ne pas l'utiliser. De cette façon, vous pouvez écrire un chemin de transfert propre dans votre code et laisser la gestion des erreurs dans un chemin de code distinct.
Spence

36
L'instruction using sert à garantir la fermeture du flux une fois que j'ai terminé. Je pense que vous trouverez que using () {} est moins de caractères que try {} finalement {obj.Dispose ()}. Vous constaterez également que vous devez maintenant déclarer votre référence d'objet en dehors de l'instruction using, qui est plus typée. Si vous avez une interface explicite, vous devrez également caster. Enfin, vous souhaitez supprimer ASAP, et la logique enfin peut avoir une interface utilisateur ou toute autre action longue qui n'a pas grand-chose à voir avec l'appel à IDispose. </rant>
Spence

2
cela ne nie pas le fait que vous devez déclarer votre objet en dehors de la tentative et appeler explicitement disposer, ce qui fait pour vous et signifie la même chose.
Spence

92

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

8
Je voterais positivement s'il n'y avait pas autant de "nombres magiques" dedans en.wikipedia.org/wiki/Magic_number_(programming)
Kris

3
Je faisais référence aux comparaisons errorCode, pas aux décalages de bits. mais maintenant vous le mentionnez ...
Kris

1
Votre Catch doit être activé IOException, plutôt que général Exception, puis un test sur le type.
Askolein

3
@JeremyThompson vous avez malheureusement mis le spécifique IOExceptionaprès le général. Le général attrapera tout ce qui passe et le spécifique IOExceptionsera toujours seul. Échangez simplement les deux.
Askolein

J'aime cette solution. Une autre suggestion: à l'intérieur de la capture comme un autre pour le if (IsFileLocked (ex)), je lancerais ex. Cela gérera ensuite le cas où le fichier n'existe pas (ou toute autre exception IOException) en lançant l'exception.
shindigo

7

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

6

Vous pouvez peut-être utiliser un FileSystemWatcher et surveiller l'événement Changed.

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


1
L'utilisation d'un FileSystemWatcher n'aide pas, car les événements Created et Changed se déclenchent au début d'une création / modification de 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'a besoin de s'exécuter via le rappel 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 d'exécuter des boucles d'exception ...

FileSystemWatcher ne gère pas très bien beaucoup de changements en même temps, alors soyez prudent avec cela.
Ben F

1
BTW, avez-vous remarqué en déboguant et en regardant les threads que MS appelle leur propre FSW "FileSystemWather"? Qu'est-ce qu'un wather de toute façon?
devlord

Précisément, j'ai eu ce problème car un service Windows avec FileSystemWatcher essaie de lire le fichier avant que le processus ne le ferme.
freedeveloper

6

Vous pouvez renvoyer une tâche qui vous donne un flux dès qu'il est 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);
}

3
Combien de secondes jusqu'à ce qu'une pile déborde de la récursivité GetStreamAsync()?
CAD bloke

@CADbloke, vous avez soulevé un très bon point. En effet, mon échantillon peut avoir une exception de dépassement de pile, au cas où un fichier ne serait pas disponible pendant longtemps. En relation avec cette réponse stackoverflow.com/questions/4513438/… , cela pourrait lever l'exception dans 5 heures.
Ivan Branets

En fonction de vos cas d'utilisation, il est préférable de lever l'exception d'E / S si, disons, 10 tentatives de lecture du fichier ont échoué. L'autre stratégie pourrait être d'augmenter le temps d'attente d'une seconde une fois que 10 tentatives ont échoué. Vous pouvez également utiliser un mélange des deux.
Ivan Branets

Je voudrais (et fais) simplement avertir l'utilisateur que le fichier est verrouillé. Ils l'ont généralement verrouillé eux-mêmes, donc ils feront probablement quelque chose. Ou pas.
CAD bloke

Dans certains cas, vous devez utiliser la stratégie de nouvelle tentative car un fichier n'est peut-être pas encore prêt. Imaginez une application de bureau pour télécharger des images dans un dossier temporaire. L'application démarre le téléchargement et en même temps vous ouvrez ce dossier dans l'explorateur de fichiers. Windows souhaite créer une miniature immédiatement et verrouiller le fichier. Dans le même temps, votre application essaie de remplacer l'image verrouillée à un autre endroit. Vous recevrez une exception si vous n'utilisez pas la stratégie de nouvelle tentative.
Ivan Branets

4

la seule façon que je sache 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 à cela, essaient simplement les boucles / catch / sleep.


1
Vous ne pouvez pas utiliser cette API sans d'abord ouvrir le fichier, auquel cas vous n'en avez plus besoin.
Harry Johnston

1
Dossier de Schrodinger.
TheLastGIS

4
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!


10
La vérification que vous effectuez est très bien; le mettre dans une fonction est trompeur. Vous ne voulez PAS utiliser une fonction comme celle-ci avant d'ouvrir un fichier. À l'intérieur de la fonction, le fichier est ouvert, vérifié et fermé. Ensuite, le programmeur suppose que le fichier est TOUJOURS correct à utiliser et essaie de l'ouvrir pour l'utiliser. C'est mauvais car il pourrait être utilisé et verrouillé par un autre processus qui a été mis en file d'attente pour ouvrir ce fichier. Entre la 1ère fois qu'il a été ouvert (pour vérification) et la 2e fois qu'il a été ouvert (pour utilisation), le système d'exploitation aurait pu planifier votre processus et exécuter un autre processus.
Lakey

3

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 en lecture seule, le code ne fonctionnera pas. Cette solution modifiée fonctionne de la manière la plus fiable, avec deux choses à garder à l'esprit (comme c'est également le cas 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 thread, vous devrez donc le verrouiller ou gérer les problèmes de thread séparément.

En gardant à l'esprit ce qui précède, cela vérifie si le fichier est 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;
}

A toujours le même problème que la réponse acceptée - elle vous indique uniquement si le fichier a été verrouillé par un autre processus à un moment donné , ce qui n'est pas une information utile. Au moment où la fonction est retournée, le résultat peut déjà être obsolète!
Harry Johnston

1
c'est vrai, on ne peut vérifier qu'à un moment donné (ou s'abonner à des événements), l'avantage de cette approche sur la solution acceptée est qu'elle peut vérifier un attribut en lecture seule et un verrou en écriture et ne pas retourner un faux positif.
rboy

3

Mis à part le travail à 3 lignes et juste pour référence: Si vous voulez 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 aiderait à déterminer quel est le processus qui a un verrou sur un fichier. La fonction RmStartSession incluse dans rstrtmgr.dll a été utilisée pour créer une session du gestionnaire de redémarrage et selon le résultat de retour, une nouvelle instance de l'objet Win32Exception est créée. Après avoir enregistré les ressources dans une session Restart Manager via la fonction RmRegisterRescources, la fonction RmGetList est appelée pour vérifier quelles applications utilisent un fichier particulier en énumérant le tableau RM_PROCESS_INFO .

Il fonctionne en se connectant à la "Session de redémarrage du gestionnaire".

Le gestionnaire de redémarrage utilise la liste des ressources enregistrées avec la session pour déterminer quelles applications et 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 service ou des structures RM_UNIQUE_PROCESS qui décrivent les applications en cours d'exécution.

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


2

D'après mon expérience, vous voulez généralement le faire, puis «protéger» vos fichiers pour faire quelque chose de fantaisiste, puis utiliser les fichiers «protégés». Si vous ne souhaitez utiliser qu'un seul fichier comme celui-ci, vous pouvez utiliser l'astuce expliquée dans la réponse de Jeremy Thompson. Cependant, si vous essayez de le faire sur de nombreux fichiers (par exemple, lorsque vous écrivez un programme d'installation), vous risquez d'être un peu blessé.

Une manière 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 modifier le nom d'un dossier si l'un des fichiers qu'il contient est utilisé. Gardez le dossier dans le même système de fichiers et cela fonctionnera comme un charme.

Notez que vous devez être conscient des façons é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' Moveéchec de votre opération. De toute évidence, la gestion correcte des erreurs (MSDN) peut vous 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 m'en tiendrai à la suggestion de verrouillage publiée par Jeremy Thompson.


Bonjour, Étant donné que l'ordre des réponses change, vous pouvez clarifier le message ci -dessus pour les lecteurs de ce QA populaire. Merci.
Jeremy Thompson

1
@JeremyThompson Vous avez raison, merci, je vais modifier le message. J'utiliserais la solution de votre part, principalement en raison de votre utilisation correcte deFileShare et de la vérification d'un verrou.
atlaste

2

Voici un code qui, autant que je sache, 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

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

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

Si vous souhaitez plus d'informations à ce sujet, consultez 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 renverra true uniquement si le verrouillage du fichier peut être exclusif pour cet objet. Mais l'application qui télécharge le fichier doit également le faire dans le verrouillage de fichier. Si l'objet est inaccessible, metod retourne false.


1
Veuillez ne pas simplement poster un outil ou une bibliothèque comme réponse. Démontrez au moins comment il résout le problème dans la réponse elle-même.
paper1111

Démo ajoutée :) Désolé @ paper1111
Tomasz Żmuda

1
Nécessite tous les processus qui utilisent le fichier pour coopérer. Peu susceptible d'être applicable au problème d'origine des PO.
Harry Johnston

2

Une fois, j'avais besoin de télécharger des fichiers PDF dans une archive de sauvegarde en ligne. Mais la sauvegarde échouerait si l'utilisateur avait ouvert le fichier dans un autre programme (tel que le lecteur PDF). Dans ma hâte, j'ai tenté quelques-unes des meilleures réponses de ce fil, mais je n'ai pas pu les faire fonctionner. Ce qui a fonctionné pour moi, c'était d'essayer de déplacer le fichier PDF dans son propre répertoire . J'ai trouvé que cela échouerait si le fichier était ouvert dans un autre programme, et si le déplacement était réussi, aucune opération de restauration ne serait nécessaire, comme si elle était déplacée vers un répertoire distinct. Je souhaite publier ma solution de base au cas où elle pourrait être 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

Je suis intéressé de voir si cela déclenche des réflexes WTF. J'ai un processus qui crée et lance ensuite un document PDF à partir d'une application console. Cependant, je faisais face à une fragilité où si l'utilisateur devait exécuter le processus plusieurs fois, en générant le même fichier sans d'abord fermer le fichier généré précédemment, l'application lèverait une exception et mourrait. C'était un événement assez fréquent car les noms de fichiers sont basés sur les numéros de devis de vente.

Plutôt que d'échouer d'une manière aussi ingrate, j'ai décidé de compter sur la gestion des versions de fichiers auto-incrémentée:

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

Un peu plus d'attention peut probablement être accordée au catchbloc pour m'assurer que j'attrape les bonnes IOException (s). J'effacerai probablement également le stockage de l'application au démarrage, car ces fichiers sont de toute façon temporaires.

Je me rends compte que cela dépasse la portée de la question du PO de vérifier simplement si le fichier est en cours d'utilisation, mais c'était effectivement le problème que je cherchais à résoudre quand je suis arrivé ici, alors peut-être qu'il sera utile à quelqu'un d'autre.


0

Est-ce que quelque chose comme ça aiderait?

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

    }
}

-2

Essayez de déplacer / copier le fichier dans un répertoire temporaire. Si vous le pouvez, il n'a pas de verrou et vous pouvez travailler en toute sécurité dans le répertoire temp sans obtenir de verrous. Sinon, essayez simplement de le déplacer à nouveau en x secondes.


@jcolebrand verrouille quoi? celui que vous avez copié? Ou celui que vous avez mis dans le répertoire temporaire?
Cullub

5
si vous copiez le fichier, en attendant que personne d'autre ne travaille dessus, et que vous utilisiez le fichier temporaire, puis que quelqu'un le verrouille juste après l'avoir copié, vous risquez de perdre des données.
jcolebrand

-3

J'utilise cette solution de contournement, mais j'ai un délai entre le moment où je vérifie le verrouillage du fichier avec la fonction IsFileLocked et lorsque j'ouvre le fichier. Dans ce laps de temps, un autre thread peut ouvrir le fichier, donc j'obtiendrai IOException.

J'ai donc ajouté du code supplémentaire pour cela. Dans mon cas, je veux charger XDocument:

        XDocument xDoc = null;

        while (xDoc == null)
        {
            while (IsFileBeingUsed(_interactionXMLPath))
            {
                Logger.WriteMessage(Logger.LogPrioritet.Warning, "Deserialize can not open XML file. is being used by another process. wait...");
                Thread.Sleep(100);
            }
            try
            {
                xDoc = XDocument.Load(_interactionXMLPath);
            }
            catch
            {
                Logger.WriteMessage(Logger.LogPrioritet.Error, "Load working!!!!!");
            }
        }

Qu'est-ce que tu penses? Puis-je changer quelque chose? Peut-être que je n'avais pas du tout besoin d'utiliser la fonction IsFileBeingUsed?

Merci


4
qu'est-ce que IsFileBeingUsed? code source sur IsFileBeingUsed?
Kiquenet
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.