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

Estoy escribiendo un progtwig en C # que necesita acceder repetidamente a 1 archivo de imagen. La mayoría de las veces funciona, pero si mi computadora funciona rápido, intentará acceder al archivo antes de que se haya guardado 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 toda mi búsqueda en Google solo ha producido la creación de cheques mediante el manejo de excepciones. Esto va en contra de mi religión, entonces me preguntaba si alguien tiene una mejor manera de hacerlo.

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

Su mejor apuesta es una captura de try / finally que intente manejar el 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é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"); 

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

No he usado esto por mí mismo, pero podría valer la pena intentarlo. Si el sistema de archivos guardián resulta ser un poco pesado para este caso, iría por el ciclo try / catch / sleep.

la única forma que conozco es usar la API de locking exclusivo 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.

 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!

Solo use la excepción como estaba previsto. Acepte que el archivo esté en uso y vuelva a intentarlo varias veces hasta que 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 expira después de 2 segundos

 private T TimeoutFileAction(Func 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 proporciona una transmisión tan pronto como esté disponible. Es una solución simplificada, pero es un buen punto de partida. Es hilo seguro.

 private async Task 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); } 

Las respuestas aceptadas anteriormente tienen un problema donde si el archivo se ha abierto 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 la manera más confiable, con dos cosas a tener en cuenta (también es cierto para la solución aceptada):

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

Teniendo en cuenta lo anterior, esto comprueba 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; } 

Aquí hay un código que, en la medida en que mejor 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 sólido hacerlo de la siguiente manera:

  public static void TryToDoWithFileStream(string file, Action 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); } 

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

Puedes 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 debe hacerlo también en el locking de archivos. Si el objeto es inaccesible, el método devuelve falso.

En mi experiencia, generalmente quiere hacer esto, luego ‘proteger’ sus archivos para hacer algo sofisticado 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 intenta hacer esto en muchos archivos (por ejemplo, cuando está escribiendo un instalador), se sentirá bastante ofendido.

Una forma muy elegante de solucionar esto es usar el hecho de que su sistema de archivos no le permitirá cambiar el nombre de una carpeta si uno de los archivos está siendo utilizado. Mantenga la carpeta en el mismo sistema de archivos y funcionará como un amuleto.

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 Move falle. Obviamente, el manejo correcto 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 locking publicada por Jeremy Thompson.

Me interesa ver si esto desencadena algún reflection WTF. Tengo un proceso que crea y posteriormente inicia 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 antes cerrar el archivo generado previamente, la aplicación lanzaría una excepción y moriría. Esta fue una ocurrencia bastante frecuente porque los nombres de los archivos se basan en los números de las cotizaciones de venta.

En lugar de fallar de una manera tan desvergonzada, decidí confiar en el control de versiones de archivo 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 pueda dar más cuidado al bloque catch para asegurar que estoy capturando la (s) IOException (es) correcta (s). Probablemente también borre el almacenamiento de la aplicación en el 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 scope de la pregunta del OP de simplemente verificar si el archivo está en uso, pero este fue de hecho el problema que estaba tratando de resolver cuando llegué aquí, así que tal vez sea útil para otra persona.

Pruebe y mueva / copie el archivo a un directorio temporal. Si puede, no tiene locking y puede trabajar de forma segura en el directorio temporal sin lockings. De lo contrario, intente moverlo nuevamente en x segundos.

Utilizo esta solución, pero tengo un intervalo de tiempo entre el momento en que compruebo el locking de archivos con la función IsFileLocked y cuando abro el archivo. En este intervalo de tiempo, otro hilo puede abrir el archivo, por lo que obtendré IOException.

Entonces, agregué un 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? Tal vez no tuve que usar la función IsFileBeingUsed en absoluto?

Gracias