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




.net file file-io file-locking (14)

Aparte de trabajar 3-liners y solo como referencia: si quieres la información completa , hay un pequeño proyecto en el Centro de desarrollo de Microsoft:

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

De la Introducción:

El código de muestra de C # desarrollado en .NET Framework 4.0 ayudaría a descubrir cuál es el proceso que tiene un bloqueo en un archivo. La función RmStartSession que se incluye en rstrtmgr.dll se ha utilizado para crear una sesión de reinicio del administrador y, según el resultado devuelto, se crea una nueva instancia del objeto Win32Exception. Después de registrar los recursos en una sesión de Restart Manager a través de la función RmRegisterRescources , se invoca la función RmGetList para verificar qué aplicaciones están utilizando un archivo en particular al enumerar la matriz RM_PROCESS_INFO .

Funciona conectándose a la "Sesión de reinicio del administrador".

El Administrador de reinicio utiliza la lista de recursos registrados con la sesión para determinar qué aplicaciones y servicios deben cerrarse y reiniciarse. Los recursos se pueden identificar por nombres de archivos, nombres cortos de servicio o estructuras RM_UNIQUE_PROCESS que describen aplicaciones en ejecución.

Puede que esté un poco sobre diseñado para tus necesidades particulares ... Pero si eso es lo que quieres, sigue adelante y toma el proyecto vs.

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.


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.
}

Use esto para verificar si un archivo está bloqueado:

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

Por razones de rendimiento, le recomiendo que lea el contenido del archivo en la misma operación. Aquí hay unos ejemplos:

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

Pruébalo tú mismo:

byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");

Pruebe y mueva / copie el archivo a un directorio temporal. Si puede, no tiene bloqueo y puede trabajar de manera segura en el directorio temporal sin obtener bloqueos. Si no, intenta moverlo de nuevo en x segundos.


Sólo tiene que utilizar la excepción según lo previsto. Acepte que el archivo está en uso e intente nuevamente, repetidamente hasta que se complete su acción. Este también es el más eficiente porque no pierdes ningún ciclo verificando el estado antes de actuar.

Usa la función de abajo, por ejemplo

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

Método reutilizable que expira después de 2 segundos.

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

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

Las respuestas aceptadas anteriormente tienen un problema en el que si el archivo se abrió para escribir con un modo de FileShare.Read o si el archivo tiene un atributo de solo lectura, el código no funcionará. Esta solución modificada funciona de manera más confiable, con dos cosas que se deben tener en cuenta (como ocurre también con la solución aceptada):

  1. No funcionará con los archivos que se han abierto con un modo de escritura compartida
  2. Esto no tiene en cuenta los problemas de subprocesos, por lo que tendrá que bloquearlo o manejar los problemas de subprocesos por separado.

Teniendo en cuenta lo anterior, esto verifica si el archivo está bloqueado para escritura o bloqueado para evitar la lectura :

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

Puedes usar mi biblioteca para acceder a archivos desde múltiples aplicaciones.

Puede instalarlo desde nuget: Install-Package Xabe.FileLock

Si desea obtener más información al respecto, visite https://github.com/tomaszzmuda/Xabe.FileLock

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

El método fileLock.Acquire devolverá verdadero solo si se puede bloquear el archivo exclusivo para este objeto. Pero la aplicación que carga el archivo debe hacerlo también en el bloqueo de archivos. Si el objeto es inaccesible, el método devuelve false.


NOTA actualizada sobre esta solución : la verificación con FileAccess.ReadWrite fallará para los archivos de solo lectura, por lo que la solución se modificó para verificar con FileAccess.Read . Si bien esta solución funciona porque intentar verificar con FileAccess.Read fallará si el archivo tiene un bloqueo de Escritura o Lectura, sin embargo, esta solución no funcionará si el archivo no tiene un bloqueo de Escritura o Lectura, es decir, ha sido abierto (para leer o escribir) con acceso a FileShare.Read o FileShare.Write.

ORIGINAL: He usado este código durante los últimos años y no he tenido ningún problema con él.

Comprenda su duda sobre el uso de excepciones, pero no puede evitarlas todo el tiempo:

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

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

La única forma que conozco es usar la API de bloqueo exclusivo de Win32, que no es demasiado rápida, pero existen ejemplos.

La mayoría de la gente, para una solución simple a esto, simplemente intenta / atrapa / duerme los bucles.


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!


Utilizar

shutil.rmtree(path[, ignore_errors[, onerror]])

(Ver documentación completa sobre shutil ) y / o

os.remove

y

os.rmdir

(Documentación completa en os.remove() .)





c# .net file file-io file-locking