c# ¿Hay alguna manera de verificar si un archivo está en uso?





8 Answers

Puede sufrir una condición de carrera de hilos en la cual hay ejemplos documentados de que se está utilizando como una vulnerabilidad de seguridad. Si verifica que el archivo esté disponible, pero luego lo intenta y lo puede usar en ese punto, que un usuario malintencionado podría usar para forzar y explotar su código.

Su mejor apuesta es una captura de prueba / finalmente que intenta obtener el identificador de archivo.

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.
}
c# .net file file-io file-locking

Estoy escribiendo un programa en C # que necesita acceder repetidamente a 1 archivo de imagen. La mayoría de las veces funciona, pero si mi computadora está funcionando rápido, intentará acceder al archivo antes de guardarlo nuevamente en el sistema de archivos y generará un error: "Archivo en uso por otro proceso" .

Me gustaría encontrar una forma de evitar esto, pero todas mis búsquedas en Google solo han permitido crear cheques mediante el manejo de excepciones. Esto va en contra de mi religión, así que me preguntaba si alguien tiene una mejor manera de hacerlo.




Quizás podría usar un FileSystemWatcher y ver el evento Changed.

Yo no he usado esto, pero podría valer la pena intentarlo. Si el filesystemwatcher resulta ser un poco pesado para este caso, me gustaría ir al circuito try / catch / sleep.




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

¡Espero que esto ayude!




Puede devolver una tarea que le proporcione una secuencia tan pronto como esté disponible. Es una solución simplificada, pero es un buen punto de partida. Es hilo seguro.

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

Puedes usar este flujo como de costumbre:

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



Aquí hay un código que, por lo que puedo decir, hace lo mismo que la respuesta aceptada pero con menos código:

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

Sin embargo creo que es más robusto hacerlo de la siguiente manera:

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



En mi experiencia, generalmente desea hacer esto, luego "proteger" sus archivos para hacer algo sofisticado y luego usar los archivos "protegidos". Si solo tiene un archivo que desea usar así, puede usar el truco que se explica en la respuesta de Jeremy Thompson. Sin embargo, si intenta hacer esto en muchos archivos (por ejemplo, cuando está escribiendo un instalador), se encontrará con un poco de daño.

Una forma muy elegante de resolverlo es mediante el hecho de que su sistema de archivos no le permitirá cambiar el nombre de una carpeta si uno de los archivos allí se está utilizando. Mantenga la carpeta en el mismo sistema de archivos y funcionará como un encanto.

Tenga en cuenta que debe ser consciente de las formas obvias en que esto puede ser explotado. Después de todo, los archivos no serán bloqueados. Además, tenga en cuenta que hay otras razones que pueden hacer que su operación de Move falle. Obviamente, el manejo adecuado de errores (MSDN) puede ayudar aquí.

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

Para archivos individuales me quedaría con la sugerencia de bloqueo publicada por Jeremy Thompson.




Estoy interesado en ver si esto desencadena algún reflejo de WTF. Tengo un proceso que crea y posteriormente lanza un documento PDF desde una aplicación de consola. Sin embargo, estaba lidiando con una fragilidad en la que si el usuario ejecutaba el proceso varias veces, generaba el mismo archivo sin cerrar primero el archivo generado anteriormente, la aplicación lanzaría una excepción y moriría. Esto sucedió con bastante frecuencia porque los nombres de los archivos se basan en números de cotización de ventas.

En lugar de fallar de una manera tan deshonrosa, decidí confiar en el control de versiones de archivos auto-incrementado:

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

Probablemente se le puede prestar más atención al bloque catch para asegurar que detecto las IOException (s) correctas. Probablemente también borraré el almacenamiento de la aplicación al iniciar, ya que estos archivos están destinados a ser temporales de todos modos.

Me doy cuenta de que esto va más allá del alcance de la pregunta del OP de simplemente verificar si el archivo está en uso, pero este fue el problema que buscaba resolver cuando llegué aquí, por lo que quizás sea útil para otra persona.




Utilizo esta solución alternativa, pero tengo un intervalo de tiempo entre cuando compruebo el bloqueo de archivos con la función IsFileLocked y cuando abro el archivo. En este intervalo de tiempo, otro hilo puede abrir el archivo, así que obtendré IOException.

Entonces, agregué código extra para esto. En mi caso quiero cargar 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é piensas? ¿Puedo cambiar algo? ¿Quizás no tuve que usar la función IsFileBeingUsed en absoluto?

Gracias




Related