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

846

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ápidamente, intentará acceder al archivo antes de que se guarde nuevamente en el sistema de archivos y arrojará un error: "Archivo en uso por otro proceso" .

Me gustaría encontrar una forma de evitar esto, pero todos mis Google solo han dado lugar a la creación de controles 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.

Dawsy
fuente
30
Muy bien, puede probarlo examinando todos los controladores abiertos en el sistema. Sin embargo, dado que Windows es un sistema operativo multitarea, existe la posibilidad de que, justo después de ejecutar el código, determine si el archivo está abierto y considere que no lo está, un código de proceso comienza a usar ese archivo, luego, cuando intente utilízalo, recibes un error. Pero, no hay nada de malo en verificar primero; simplemente no asuma que no está en uso cuando realmente lo necesita.
BobbyShaftoe
3
Pero solo por este tema específico; Recomiendo no examinar los identificadores de los archivos e intentarlo varias veces, digamos 3-5 antes de fallar.
BobbyShaftoe
¿Cómo se genera este archivo de imagen? ¿Puede detener / dormir / pausar su programa hasta que se complete la generación? Esa es, con mucho, una forma superior de manejar la situación. Si no, entonces no creo que pueda evitar el uso de manejo de excepciones.
Catchwa
¿No es todo el uso de excepciones una comprobación de alguna suposición al hacer algo potencialmente peligroso sin descartar deliberadamente la posibilidad de falla?
jwg
26
Su filosofía tiene una mala comprensión de las excepciones. La mayoría de la gente piensa que las excepciones significan santo-basura-de-destino-algo-está-mal-muere-muere-muere. Cuando excepción significa ... excepción. Significa que ocurrió algo excepcional que debe "manejar" (o tener en cuenta). Tal vez desee volver a intentar el acceso a los datos, tal vez el usuario necesite saber que no puede obtener una conexión. ¿Qué haces? Usted maneja la ConnectionFailedException y notifica al usuario, por lo que tal vez, dejarán de intentarlo después de una hora y notarán que el cable está desconectado.
Lee Louviere

Respuestas:

543

NOTA actualizada sobre esta solución : la verificación con FileAccess.ReadWritefallará para los archivos de solo lectura, por lo que la solución se ha modificado para verificar conFileAccess.Read . Si bien esta solución funciona porque al intentar verificar FileAccess.Readfallará 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, se ha abierto (para leer o escribir) con acceso 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 sus dudas sobre el uso de excepciones, pero no puede evitarlas todo el tiempo:

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;
}
ChrisW
fuente
6060
Esta es una gran solución, pero tengo un comentario: es posible que no desee abrir el archivo con el modo de acceso FileAccess. Lea ya que ReadWrite siempre fallará si el archivo es de solo lectura.
adeel825
220
-1. Esta es una respuesta pobre, porque el archivo podría quedar bloqueado por otro hilo / proceso después de que se cierra en IsFileLocked, y antes de que su hilo tenga la oportunidad de abrirlo.
Polyfun
16
Creo que esta es una gran respuesta. Estoy usando esto como un método de extensión a la public static bool IsLocked(this FileInfo file) {/*...*/}.
Manuzor
54
@ChrisW: quizás te estés preguntando qué está pasando. No te alarmes. Estás siendo objeto de la ira de la comunidad Daily WTF: thedailywtf.com/Comments/…
Pierre Lebeaupin
16
@ChrisW ¿Por qué es eso algo malo? Esta comunidad está aquí para señalar respuestas buenas y malas. Si un grupo de profesionales se da cuenta de que esto es algo malo y se une para votar negativamente, entonces el sitio es WAI. Y antes de que te pongas negativo, si lees ese artículo, te dicen que "subaste la respuesta correcta" y no la baja. ¿Quieres que también expliquen sus votos a favor en los comentarios? ¡Gracias por presentarme a otro buen sitio!
Lee Louviere
570

Puede sufrir una condición de carrera de hilo en esto, que hay ejemplos documentados de que esto se utiliza como una vulnerabilidad de seguridad. Si comprueba que el archivo está disponible, pero luego intenta usarlo, podría lanzarlo en ese punto, que un usuario malintencionado podría usar para forzar y explotar su código.

Su mejor opción es intentar capturar / 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.
}
Spence
fuente
124
+1. No hay una forma 100% segura de "saber si un archivo está en uso" porque milisegundos después de hacer la verificación, es posible que el archivo ya no esté en uso o viceversa. En su lugar, simplemente abre el archivo y lo usa si no hay excepciones.
Sedat Kapanoglu
8
Lástima .NET no es compatible con CAS. Algo así como, TryOpenFile (Ref FileHandle) que devuelve éxito / fracaso. Siempre debe haber una solución alternativa que no dependa solo del manejo de excepciones. Me pregunto cómo lo hace Microsoft Office.
TamusJRoyce
2
La clave para entender aquí es que esta API simplemente está usando la API de Windows para obtener un identificador de archivo. Como tal, deben traducir el código de error recibido de la API de C y ajustarlo a una excepción para lanzar. Tenemos manejo de excepciones en .Net, entonces ¿por qué no usarlo? De esa manera, puede escribir una ruta de reenvío limpia en su código y dejar el manejo de errores en una ruta de código separada.
Spence
36
La declaración de uso es para garantizar que la secuencia se cierre una vez que haya terminado. Creo que encontrará que el uso () {} tiene menos caracteres que el intento {} finalmente {obj.Dispose ()}. También encontrará que ahora necesita declarar su referencia de objeto fuera de la declaración de uso, que es más tipeo. Si tienes una interfaz explícita, también deberías emitir. Finalmente, desea deshacerse lo antes posible, y la lógica finalmente puede tener una interfaz de usuario o cualquier otra acción de ejecución prolongada que tenga poco que ver con llamar a IDispose. </rant>
Spence
2
eso no niega el hecho de que tiene que declarar su objeto fuera del intento y tiene que llamar explícitamente a dispose, que usar hace para usted y significa lo mismo.
Spence
92

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");
Jeremy Thompson
fuente
8
Votaría si no hubiera tantos "números mágicos" allí en.wikipedia.org/wiki/Magic_number_(programming)
Kris
3
Me refería a las comparaciones de código de error, no a los cambios de bits. aunque ahora lo mencionas ...
Kris
1
Su captura debe estar activada IOException, en lugar de general Exceptiony luego una prueba de tipo.
Askolein
3
@JeremyThompson lamentablemente pones el específico IOExceptiondespués del general. El general atrapará todo lo que pasa y el específico IOExceptionsiempre estará solo. Solo intercambia los dos.
Askolein
Me gusta esta solución Otra sugerencia: dentro de la captura como otra cosa al if (IsFileLocked (ex)) arrojaría ex. Esto manejará el caso donde el archivo no existe (o cualquier otra IOException) lanzando la excepción.
shindigo
7

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

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

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

Método reutilizable que agota 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);
}
kernowcode
fuente
6

Quizás podría usar un FileSystemWatcher y observar el evento Modificado.

No lo he usado yo mismo, pero podría valer la pena intentarlo. Si el sistema de archivos resulta ser un poco pesado para este caso, iría al bucle try / catch / sleep.

Karl Johan
fuente
1
El uso de un FileSystemWatcher no ayuda, porque los eventos Creado y Modificado se generan al comienzo de la creación / cambio de un archivo. Incluso los archivos pequeños necesitan más tiempo para ser escritos y cerrados por el sistema operativo que la aplicación .NET necesita ejecutarse a través de la devolución de llamada FileSystemEventHandler. Esto es muy triste, pero no hay otra opción que la de estimar el tiempo de espera antes de acceder al archivo o correr en bucles de excepción ...
FileSystemWatcher no maneja muchos cambios al mismo tiempo muy bien, así que ten cuidado con eso.
Ben F
1
Por cierto, ¿notaron ustedes mientras depuraban y miraban hilos que MS llama a su propio FSW "FileSystemWather"? ¿Qué es un wather de todos modos?
devlord
Precisamente recibí este problema porque un servicio de Windows con FileSystemWatcher intenta leer el archivo antes de que el proceso lo cierre.
freedeveloper
6

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

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 usar esta transmisión como de costumbre:

using (var stream = await FileStreamGetter.GetStreamAsync())
{
    Console.WriteLine(stream.Length);
}
Ivan Branets
fuente
3
¿Cuántos segundos hasta que una pila se desborde de la recursividad GetStreamAsync()?
CAD bloke
@CADbloke, has planteado un muy buen punto. De hecho, mi muestra podría tener una excepción de desbordamiento de pila, en caso de que un archivo no esté disponible durante mucho tiempo. En relación con esta respuesta stackoverflow.com/questions/4513438/… , podría generar la excepción en 5 horas.
Ivan Branets
En relación con sus casos de uso, es preferible lanzar una excepción de E / S si, digamos, 10 intentos de leer el archivo han fallado. La otra estrategia podría ser aumentar el tiempo de espera por un segundo una vez que hayan fallado 10 intentos. También puedes usar una mezcla de ambos.
Ivan Branets
Simplemente (y lo haría) simplemente alertar al usuario de que el archivo está bloqueado. Generalmente lo cerraron ellos mismos, por lo que probablemente harán algo al respecto. O no.
CAD bloke
En algunos casos, debe usar la política de reintento, ya que es posible que un archivo aún no esté listo. Imagine una aplicación de escritorio para descargar imágenes en una carpeta temporal. La aplicación comienza a descargarse y al mismo tiempo abre esta carpeta en el explorador de archivos. Windows quiere crear una miniatura de inmediato y bloquear el archivo. Al mismo tiempo, su aplicación intenta reemplazar la imagen bloqueada en otro lugar. Recibirá una excepción si no utiliza la política de reintentos.
Ivan Branets
4

Lo único que sé es utilizar la API de bloqueo exclusiva de Win32, que no es demasiado rápida, pero existen ejemplos.

La mayoría de las personas, para una solución simple a esto, simplemente para probar / atrapar / dormir bucles.

Luke Schafer
fuente
1
No puede usar esta API sin abrir primero el archivo, momento en el que ya no necesita hacerlo.
Harry Johnston
1
Archivo 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");

¡Espero que esto ayude!

Julian
fuente
10
La verificación real que realiza está bien; ponerlo dentro de una función es engañoso. NO desea utilizar una función como esta antes de abrir un archivo. Dentro de la función, el archivo se abre, se verifica y se cierra. Luego, el programador ASUME que el archivo TODAVÍA está bien para usarlo e intenta abrirlo para usarlo. Esto es malo porque podría ser utilizado y bloqueado por otro proceso que se puso en cola para abrir este archivo. Entre la primera vez que se abrió (para verificar) y la segunda vez que se abrió (para uso), el sistema operativo podría haber reprogramado su proceso y podría estar ejecutando otro proceso.
Lakey
3

Las respuestas aceptadas anteriormente sufren un problema en el que si el archivo se abrió para escribir con un modo 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 a tener en cuenta (como también es cierto para la solución aceptada):

  1. No funcionará para archivos que se han abierto con un modo de escritura compartida
  2. Esto no tiene en cuenta los problemas de subprocesos, por lo que deberá bloquearlo o manejarlos 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;
}
rboy
fuente
Todavía tiene el mismo problema que la respuesta aceptada: solo le dice si el archivo fue bloqueado por otro proceso en un momento determinado en el tiempo , lo que no es información útil. Para cuando la función haya regresado, ¡el resultado ya puede estar desactualizado!
Harry Johnston
1
eso es cierto, solo se puede verificar en un momento dado (o suscribirse a eventos), la ventaja de este enfoque sobre la solución aceptada es que puede verificar un atributo de solo lectura y un bloqueo de escritura y no devolver un falso positivo.
rboy
3

Además de trabajar 3 líneas y solo como referencia: si desea la información completa , 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 código de muestra 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 administrador de reinicio y, según el resultado de la devolución, se crea una nueva instancia del objeto Win32Exception. Después de registrar los recursos para una sesión de Administrador de reinicio a través RmRegisterRescources función, RmGetList función se invoca para comprobar cuáles son las aplicaciones están utilizando un archivo en particular mediante la enumeración de la RM_PROCESS_INFO matriz.

Funciona conectándose a la "Reiniciar sesión 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 archivo, nombres cortos de servicio o estructuras RM_UNIQUE_PROCESS que describen aplicaciones en ejecución.

Podría estar un poco diseñado para sus necesidades particulares ... Pero si eso es lo que desea, continúe y tome el proyecto vs.

Bernhard
fuente
2

En mi experiencia, generalmente desea hacer esto, luego 'proteger' sus archivos para hacer algo elegante y luego usar los archivos 'protegidos'. Si solo tiene un archivo que desea usar de esta manera, puede usar el truco que se explica en la respuesta de Jeremy Thompson. Sin embargo, si intentas hacer esto en muchos archivos (por ejemplo, cuando escribes un instalador), te dolerá un poco.

Una manera muy elegante de resolver esto es usando el hecho de que su sistema de archivos no le permitirá cambiar el nombre de una carpeta si se está usando uno de los archivos. Mantenga la carpeta en el mismo sistema de archivos y funcionará como un encanto.

Tenga en cuenta que debe tener en cuenta las formas obvias de explotar esto. Después de todo, los archivos no serán bloqueados. Además, tenga en cuenta que hay otras razones que pueden provocar que su Moveoperación 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.

atlaste
fuente
Hola, dado que el orden de las respuestas cambia, ¿puedes aclarar qué publicación anterior quieres decir para los lectores de este popular QA? Gracias.
Jeremy Thompson
1
@JeremyThompson Tienes razón, gracias, editaré la publicación. Usaría la solución de usted, principalmente debido a su uso correcto FileSharey la comprobación de un bloqueo.
atlaste
2

Aquí hay un código que, por lo que puedo decir mejor, 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);
    }
cdiggins
fuente
2

Puede 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, consulte 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 puede bloquear el archivo exclusivo para este objeto. Pero la aplicación que carga el archivo también debe hacerlo en el bloqueo de archivos. Si el objeto es inaccesible, el método devuelve falso.

Tomasz Żmuda
fuente
1
Por favor, no solo publique alguna herramienta o biblioteca como respuesta. Al menos demuestre cómo resuelve el problema en la respuesta misma.
documento1111
Demo añadida :) Lo siento @ paper1111
Tomasz Żmuda
1
Requiere todos los procesos que están utilizando el archivo para cooperar. Es poco probable que sea aplicable al problema original de los OP.
Harry Johnston
2

Una vez necesitaba subir archivos PDF a un archivo de respaldo en línea. Pero la copia de seguridad fallaría si el usuario tuviera el archivo abierto en otro programa (como un lector de PDF). En mi apuro, intenté algunas de las principales respuestas en este hilo, pero no pude hacer que funcionaran. Lo que funcionó para mí fue tratar de mover el archivo PDF a su propio directorio . Descubrí que esto fallaría si el archivo estuviera abierto en otro programa, y ​​si el movimiento fuera exitoso, no se requeriría una operación de restauración, ya que si se moviera a un directorio separado. Quiero publicar mi solución básica en caso de que pueda ser útil para los casos de uso específicos de otros.

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
}
Benjamin Curtis Drake
fuente
0

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

En lugar de fallar de una manera tan desagradable, decidí confiar en el versionado automático de archivos:

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 pueda prestar más atención al catchbloque para asegurarme de que estoy captando las IOException (s) correctas. Probablemente también borre el almacenamiento de la aplicación al inicio 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 estaba tratando de resolver cuando llegué aquí, por lo que tal vez sea útil para otra persona.

Vinney Kelly
fuente
0

¿Algo como esto ayudaría?

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

    }
}
Tadej
fuente
-2

Intente mover / copiar el archivo a un directorio temporal. Si puede, no tiene bloqueo y puede trabajar de forma segura en el directorio temporal sin obtener bloqueos. De lo contrario, intenta moverlo nuevamente en x segundos.

Carra
fuente
@jcolebrand bloquea qué? el que copiaste? ¿O el que pones en el directorio temporal?
Cullub
55
si copia el archivo, esperando que nadie más esté trabajando en él, y va a usar el archivo temporal, y luego alguien lo bloquea justo después de copiarlo, entonces posiblemente haya perdido datos.
jcolebrand
-3

Utilizo esta solución, pero tengo un intervalo de tiempo entre cuando verifico el bloqueo del archivo con la función IsFileLocked y cuando abro el archivo. En este intervalo de tiempo, algún otro hilo puede abrir el archivo, por lo que obtendré IOException.

Entonces, agregué código adicional 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

zzfima
fuente
44
¿Qué es IsFileBeingUsed? código fuente sobre IsFileBeingUsed?
Kiquenet