Pregunta

Estoy escribiendo un programa en C# que necesita para acceder repetidamente 1 archivo de imagen.La mayoría de las veces funciona, pero si mi pc corriendo más rápido, se va a tratar de acceder a los archivos antes de que se haya guardado la espalda al sistema de archivos y se producirá un error: "Archivo en uso por otro proceso".

Me gustaría encontrar una forma de evitar esto, pero todos mis Googlear sólo ha cedido la creación de cheques mediante el manejo de excepciones.Esto es en contra de mi religión, así que me preguntaba si alguien tiene una mejor forma de hacerlo?

¿Fue útil?

Solución

NOTA Actualizado en esta solución : Comprobación con FileAccess.ReadWrite fallará para archivos de sólo lectura por lo que la solución se ha modificado para comprobar con FileAccess.Read. Mientras que esta solución funciona porque tratar de comprobar con FileAccess.Read fallará si el archivo tiene una escritura o bloqueo de lectura en él, sin embargo, esta solución no funcionará si el archivo no tiene una escritura o bloqueo de lectura en él, es decir, que ha sido abierto (para lectura o escritura) con FileShare.Read o FileShare.Write acceso.

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

Entender su vacilación sobre el uso de excepciones, pero no se puede evitar que 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;
}

Otros consejos

Puede sufrir de una condición de carrera en este hilo de los cuales se han documentado ejemplos de esto se utiliza como una vulnerabilidad de seguridad. Si comprueba que el archivo está disponible, pero luego lo prueba y utiliza que podría lanzar en ese punto, que un usuario malintencionado podría utilizar para forzar y explotar en el código.

Su mejor apuesta es un intento de captura / fin que trata de 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.
}

Utilice esta opción para comprobar 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 motivos de rendimiento le recomiendo que lea el contenido del archivo en la misma operación. He aquí algunos 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ébelo usted 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");

Tal vez usted podría utilizar un FileSystemWatcher y reloj Se cambió para el evento.

No he utilizado yo mismo, pero podría ser digno de un tiro. Si el FileSystemWatcher resulta ser un poco pesado para este caso, me gustaría ir para la captura / bucle try / sueño.

Sólo tiene que utilizar la excepción según lo previsto. Aceptar que el archivo está en uso y vuelve a intentarlo varias veces hasta que se termina su acción. Esto también es el más eficiente debido a que no pierda ningún ciclos de comprobación del estado antes de actuar.

Utilice la función a continuación, por ejemplo

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

método reutilizable que el tiempo de espera 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);
}

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

La mayoría de las personas, para una solución simple para esto, simplemente para probar / bucles de captura / del sueño.

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 da una corriente tan pronto como esté disponible. Es una solución simplificada, pero es un buen punto de partida. Es de subprocesos.

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

Puede utilizar esta corriente como de costumbre:

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

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

  1. No funcionará para los archivos que se ha abierto con un modo de cuota de escribir
  2. Esto no toma en cuenta las cuestiones de roscado por lo que necesitará para bloquear hacia abajo o manejar problemas de threads por separado.

Mantener en cuenta lo anterior, este comprueba si el archivo es bloqueado por escribir o bloqueada para impedir 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;
}

Aparte de trabajo 3-camisas y apenas para la referencia:Si desea que el completo soplado información - hay un pequeño proyecto en Microsoft Dev Center:

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

De la Introducción:

El ejemplo de C# el código desarrollado en .NET Framework 4.0 ayudaría en averiguar cual es el proceso que está teniendo un bloqueo en un archivo. RmStartSession la función que se incluye en rstrtmgr.dll ha sido se utiliza para crear un administrador de reinicio de sesión y de acuerdo a la vuelta resultado una nueva instancia de Win32Exception objeto es creado.Después de el registro de los recursos a que se Reinicie el Administrador de sesión a través de RmRegisterRescources la función, RmGetList se invoca la función para comprobar ¿cuáles son las aplicaciones que están usando un archivo en particular mediante la enumeración de el RM_PROCESS_INFO de la matriz.

Funciona mediante la conexión a la "Administrador de Reinicio de Sesión".

El Reinicio utiliza el Administrador de la lista de recursos registrados con la sesión determinar qué aplicaciones y servicios se debe apagar y reiniciar. Los recursos pueden ser identificados por los nombres de archivo, servicio de nombres cortos, o RM_UNIQUE_PROCESS estructuras que describen las aplicaciones en ejecución.

Podría ser un poco overengineered para sus necesidades particulares...Pero si eso es lo usted quiero, seguir adelante y tomar el vs-proyecto.

Aquí hay un código que en cuanto a lo que pueda mejor cuentan 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 que 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);
    }

Puede utilizar mi biblioteca para acceder a archivos desde múltiples aplicaciones.

Puede instalarlo desde Nuget: Instalar Paquete Xabe.FileLock

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

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

fileLock.Acquire método devolverá true sólo si se puede bloquear el archivo exclusiva para este objeto. Sin embargo, la aplicación de carga de archivos, que debe hacerlo de bloqueo de archivo también. Si el objeto es inaccesible metod devuelve falso.

En mi experiencia, por lo general quieren hacer esto, entonces 'proteger' a sus archivos para hacer algo de fantasía y luego utilizar los archivos protegidos ''. Si usted tiene un solo archivo que desea utilizar como éste, puede utilizar el truco que se explica en la respuesta por Jeremy Thompson. Sin embargo, si intenta hacer esto en un montón de archivos (por ejemplo, por ejemplo, cuando se está escribiendo un instalador), usted se encontrará con un poco de dolor.

Una forma muy elegante esto puede ser resuelto mediante el uso es el hecho de que el sistema de archivos no se permitirá cambiar un nombre de carpeta si no se está utilizando uno de los archivos. Mantenga la carpeta en el mismo sistema de archivos y que funcionará como un encanto.

Ten en cuenta que usted debe ser consciente de las maneras obvias 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 resultar en su operación Move a fallar. Obviamente manejo de errores adecuado (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 los archivos individuales que me quedo con la sugerencia de bloqueo publicado por Jeremy Thompson.

Estoy interesado en ver si esto desencadena ningún reflejos WTF. Tengo un proceso que crea y posteriormente lanza un documento PDF desde una aplicación de consola. Sin embargo, yo estaba tratando con una fragilidad en la que si el usuario ejecute el proceso varias veces, generando el mismo archivo sin haber cerrado el archivo generado anteriormente, la aplicación sería una excepción y mueren. Este fue un hecho bastante frecuente debido a los nombres de archivo se basan en los números de oferta de venta.

En lugar de no de una manera tan poco elegante, decidí confiar en las versiones de archivos auto-incrementales:

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 algún tipo de atención más se le puede dar al bloque catch para asegurar que estoy agarrar la IOException (s) correcta. Voy probablemente también limpiar el almacenamiento de la aplicación en el arranque, ya que estos archivos están diseñados para ser temporales de todos modos.

Sé que esto va más allá del alcance de la cuestión de simplemente para comprobar si el archivo está en uso, pero esto era de hecho el problema que estaba buscando para resolver cuando llegué aquí, así que quizás sea útil a alguien más de la OP.

¿Podría algo como esto ayuda?

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

    }
}

Yo una vez necesario para subir archivos Pdf a un archivo de copia de seguridad.Pero la copia de seguridad podría fallar si el usuario ha abierto el archivo en otro programa (como lector de PDF).En mi prisa, he intentado algunas de las principales respuestas en este hilo, pero no podía hacerlos funcionar.¿Qué hizo el trabajo para mí, estaba tratando de mover el archivo PDF a su propio directorio.He encontrado que esto produciría un error si el archivo fue abierto en otro programa, y si el movimiento no tuvieron éxito no habría restaurar la operación requerida como si se mueve a un directorio diferente.Quiero publicar mi solución básica en caso de que pueda ser útil para otros casos de uso específicos.

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
}

Trate de mover / copiar el archivo en un directorio temporal. Si se puede, no tiene cerradura y se puede trabajar con seguridad en el directorio temporal sin obtener cerraduras. Los demás sólo tratar de moverlo de nuevo en x segundos.

Me utilizar esta solución, pero tengo un lapso de tiempo entre el momento puedo comprobar el archivo de bloqueo con función de IsFileLocked y cuando abro el archivo. En este lapso de tiempo algún otro flujo puede abrir el archivo, por lo que obtendrá IOException.

Por lo tanto, he añadido código extra para esto. En mi caso quiero XDocument carga:

        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é opinas? ¿Puedo cambiar algo? Tal vez yo no tenía que usar la función IsFileBeingUsed en absoluto?

Gracias

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top