Question

J'écris un programme en C # qui a besoin d'accéder à plusieurs reprises 1 fichier image. La plupart du temps cela fonctionne, mais si mon ordinateur courir vite, il va essayer d'accéder au fichier avant qu'il ne soit été enregistrée vers le système de fichiers et lancer une erreur: « Fichier utilisé par un autre processus » .

Je voudrais trouver un moyen de contourner cela, mais tout mon googler n'a cédé la création de chèques en utilisant la gestion des exceptions. Ceci est contraire à ma religion, je me demandais si quelqu'un a une meilleure façon de le faire?

Était-ce utile?

La solution

Mise à jour NOTE sur cette solution : Vérification avec FileAccess.ReadWrite échouera pour les fichiers en lecture seule si la solution a été modifiée pour vérifier avec FileAccess.Read. Bien que cette solution fonctionne, car en essayant de vérifier avec FileAccess.Read échouera si le fichier a une écriture ou verrou en lecture sur elle, cependant, cette solution ne fonctionnera pas si le fichier ne dispose pas d'une écriture ou d'un lock Lire à ce sujet, à savoir qu'il a été ouvert (en lecture ou en écriture) avec accès FileShare.Read ou FileShare.Write.

ORIGINAL: Je l'ai utilisé ce code depuis plusieurs années, et je ne l'ai pas eu de problèmes avec elle.

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

Autres conseils

Vous pouvez souffrir d'une condition de course de fil sur ce qui il y a des exemples documentés de cet être utilisé comme une vulnérabilité de sécurité. Si vous vérifiez que le fichier est disponible, mais essayez de l'utiliser, vous pouvez jeter à ce moment-là, qu'un utilisateur malveillant pourrait utiliser pour forcer et exploiter dans votre code.

Votre meilleur pari est une prise try / finally qui tente 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.
}

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

Peut-être que vous pouvez utiliser un FileSystemWatcher et montre pour l'événement Changed.

Je ne l'ai pas utilisé moi-même, mais il pourrait être vaut le coup. Si le FileSystemWatcher se révèle être un peu lourd pour ce cas, je pencherais pour le try / catch / boucle de sommeil.

Il suffit d'utiliser l'exception comme prévu. Accepter que le fichier est utilisé et essayez à nouveau, jusqu'à ce que votre action soit terminée. Ceci est aussi le plus efficace parce que vous ne perdez pas de cycles de vérification de l'état avant d'agir.

Utilisez la fonction ci-dessous, par exemple

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

Procédé réutilisable fois au bout de 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);
}

la seule façon que je connaisse est d'utiliser l'API de verrou exclusif Win32 qui est pas trop rapide, mais il existe des exemples.

La plupart des gens, une solution simple à cela, il suffit d'essayer / catch / boucles de sommeil.

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

Hope this helps!

Vous pouvez retourner une tâche qui vous donne un cours d'eau dès qu'il sera disponible. Il est une solution simplifiée, mais il est un bon point de départ. Il 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);
}

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

  1. Il ne fonctionne pas pour les fichiers qui ont été ouverts avec un mode de partage d'écriture
  2. Cela ne prend pas en problèmes de filetage de compte afin que vous aurez besoin de le verrouiller ou traiter des questions de filetage séparément.

Garder ce qui précède à l'esprit, ce vérifie si le fichier est verrouillé pour écrire ou verrouillée 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;
}

En plus de 3 paquebots de travail et juste pour la référence: Si vous voulez que le soufflé complet informations - il y a un petit projet sur Microsoft Dev Center:

https://code.msdn.microsoft .com / windowsapps / How-to-savoir-le-processus-704839f4

De l'introduction:

  

Le code C # exemple développé dans .NET Framework 4.0 contribuerait à   savoir qui est le processus qui a un verrou sur un fichier.    RmStartSession fonction qui est inclus dans rstrtmgr.dll a été   utilisé pour créer une session de gestionnaire de redémarrage et en fonction du rendement   résultat une nouvelle instance d'objet Win32Exception est créé. Après   l'enregistrement des ressources à une session du Gestionnaire de redémarrage via    RmRegisterRescources fonction RmGetList fonction est invoquée pour vérifier   Quelles sont les applications utilisent un fichier particulier en dénombrant    RM_PROCESS_INFO array.

Il fonctionne en se connectant à la "Restart Session Manager".

  

Le redémarrage Manager utilise la liste des ressources inscrits à la session   déterminer les applications et les services doivent être arrêtés et redémarrés.    Les ressources peuvent être identifiées par des noms de fichiers, courts noms de service, ou   structures RM_UNIQUE_PROCESS qui décrivent les applications en cours d'exécution.

Il est peut-être un peu overengineered pour vos besoins particuliers ... Mais si tel est veulent, aller de l'avant et prendre le contre-projet.

Voici un code qui, autant que je peux le mieux tell 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 pour 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);
    }

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

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

Si vous voulez plus d'informations à ce sujet vérifier https://github.com/tomaszzmuda/Xabe.FileLock

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

méthode fileLock.Acquire retourne vrai que si peut verrouiller le fichier exclusif pour cet objet. Mais l'application qui doit le faire fichier ajout dans verrouillage de fichiers aussi. Si l'objet est inaccessible metod retourne false.

Dans mon expérience, vous voulez habituellement le faire, puis « protéger » vos fichiers à faire quelque chose de fantaisie, puis utiliser les fichiers « protégés ». Si vous avez un seul fichier que vous voulez utiliser comme cela, vous pouvez utiliser l'astuce qui est expliqué dans la réponse par Jeremy Thompson. Toutefois, si vous tentez de le faire sur des lots de fichiers (par exemple, par exemple lorsque vous écrivez un installateur), vous êtes un peu de mal.

Une façon très élégante, cela peut être résolu en utilisant est le fait que votre système de fichiers ne vous permettra pas de changer un nom de dossier si l'un des fichiers là, il est utilisé. Gardez le dossier dans le même système de fichiers et ça va fonctionner comme un charme.

Prenez note que vous devez être conscient des façons évidentes, cela peut être exploité. Après tout, les fichiers ne seront pas verrouillés. De plus, sachez qu'il existe d'autres raisons qui peuvent entraîner le fonctionnement de votre Move à l'échec. Il est évident que la gestion des erreurs appropriée (MSDN) peut aider.

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 bâton avec la suggestion de verrouillage publié par Jeremy Thompson.

Je suis curieux 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 de la console. Cependant, je traitais avec une fragilité où si l'utilisateur devait exécuter le processus plusieurs fois, générer le même fichier sans fermer le fichier généré précédemment, l'application jetterait une exception et mourir. Ce fut un événement assez fréquent parce que les noms de fichiers sont basés sur le nombre de devis de vente.

Plutôt que de faillir à une telle manière disgracieuse, j'ai décidé de compter sur fichier auto-incrémentée versioning:

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

Probablement un peu plus de soins peut être donnée au bloc catch pour assurer que je suis attraper le IOException correct (s). Je vais probablement effacer aussi le stockage de l'application au démarrage car ces fichiers sont destinés à être temporaire de toute façon.

Je sais que cela va au-delà de la portée de la question de la simple vérification de l'OP si le fichier est utilisé, mais ce fut bien le problème que je cherchais à résoudre quand je suis arrivé ici, donc ce sera peut-être utile à quelqu'un d'autre.

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

    }
}

une fois besoin de télécharger des fichiers PDF à une archive de sauvegarde en ligne. Mais la sauvegarde échouera si l'utilisateur a le fichier ouvert dans un autre programme (comme lecteur PDF). Dans ma hâte, je tentais quelques-unes des réponses haut dans ce fil mais n'a pas pu les amener à travailler. Qu'est-ce que le travail pour moi était en train de déplacer le fichier PDF à son propre répertoire . Je trouve que cela échouera si le fichier était ouvert dans un autre programme, et si le mouvement ont réussi il n'y aurait pas de restauration coopération nécessaire car il y aurait si elle était déplacé dans un répertoire distinct. Je veux poster ma solution de base dans le cas où il peut être utile pour des autres cas d'utilisation spécifiques.

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
}

Essayez et déplacer / copier le fichier dans un répertoire temp. Si vous le pouvez, il n'a pas de verrouillage et vous pouvez travailler en toute sécurité dans le répertoire temporaire sans se verrouille. essayez d'autre juste pour le déplacer à nouveau en x secondes.

J'utiliser cette solution de contournement, mais j'ai un laps de temps entre le moment où je vérifie le fichier de verrouillage avec fonction IsFileLocked et quand j'ouvre le fichier. Dans ce laps de temps un autre thread peut ouvrir le fichier, je vais donc obtenir IOException.

Alors, j'ai ajouté du code supplémentaire pour cela. Dans mon cas, je veux charge 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!!!!!");
            }
        }

Que pensez-vous? Puis-je changer quelque chose? Peut-être que je ne devais pas utiliser la fonction IsFileBeingUsed du tout?

Merci

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top