2008-11-05 11 views
94

Básicamente, me gustaría verificar si tengo derechos para abrir el archivo antes de tratar de abrirlo; No quiero usar un try/catch para esta verificación a menos que sea necesario. ¿Hay alguna propiedad de acceso a archivos que pueda verificar de antemano?¿cómo se puede verificar fácilmente si se deniega el acceso para un archivo en .NET?

+2

Título cuando cambié la etiqueta: "estoy corrigiendo". No es broma. –

+5

De acuerdo, me gustaría que hubiera un TryOpen (es decir, el patrón Try-Parse). – Tristan

Respuesta

142

Lo he hecho innumerables veces en el pasado, y casi cada vez que lo he hecho me equivoqué al intentarlo.

Los permisos de archivos (incluso existencia del archivo) son volátiles — pueden cambiar en cualquier momento. Gracias a la Ley de Murphy, este especialmente incluye el breve período entre el momento en que revisa el archivo y cuando intenta abrirlo. Un cambio es aún más probable si se encuentra en un área donde sabe que necesita verificar primero. Sin embargo, curiosamente, nunca sucederá en sus entornos de prueba o desarrollo, que tienden a ser bastante estáticos. Esto hace que el problema sea más difícil de rastrear más tarde y facilita que este tipo de error lo produzca.

Lo que esto significa es que todavía tiene que ser capaz de manejar la excepción si los permisos de archivo o existencia son malos, a pesar de su verificación. El código de manejo de excepciones es requerido, ya sea que compruebe o no los permisos del archivo por adelantado. El código de manejo de excepciones proporciona todos de la funcionalidad de existencia o controles de permisos. Además, aunque se sabe que los manejadores de excepciones como este son lentos, es importante recordar que el disco de E/S es incluso más lento ... un lote más lento ... y llamar a la función .Exists() o verificar los permisos forzará una viaje adicional al sistema de archivos.

En resumen, una comprobación inicial antes de intentar abrir el archivo es redundante y derrochador. No existe un beneficio adicional sobre el manejo de excepciones, en realidad perjudicará, no ayudará, su rendimiento, agrega costos en términos de más código que debe mantenerse y puede introducir errores sutiles en su código. Simplemente no hay ninguna ventaja para hacer el control inicial. En cambio, lo correcto aquí es simplemente tratar de abrir el archivo y poner su esfuerzo en un buen manejador de excepciones si falla. Lo mismo es cierto incluso si solo está comprobando si el archivo existe o no. Este razonamiento se aplica a cualquier recurso volátil.

+3

Exactamente. Este es un ejemplo clásico de una condición de carrera. – Powerlord

+0

Sería genial si hubiera un "File System Mutex". Pero sin la existencia de eso, estás en lo cierto. –

+0

O use linux/unix, por supuesto. – chrissie1

3

Eric Lippert probablemente explica por qué Joel es correcto here

+0

Jeje, lo leí hace un tiempo y también estaba pensando en esta publicación exacta, porque esta es exactamente una molesta excepción. Pero todavía estamos esperando un método File.TryOpen() incorporado, e incluso entonces puede que tenga que tener cuidado si permite el acceso compartido suficiente. –

3

En primer lugar, lo que dijo Joel Coehoorn.

También: debe examinar las suposiciones que subyacen a su deseo de evitar el uso de try/catch a menos que sea necesario. La razón típica para evitar la lógica que depende de las excepciones (crear objetos Exception tiene un bajo rendimiento) probablemente no sea relevante para el código que abre un archivo.

Supongo que si está escribiendo un método que rellena un archivo List<FileStream> abriendo todos los archivos en un subárbol de directorio y esperaba que no se pudiera acceder a un gran número de ellos, debería verificar los permisos de archivo antes de intentar abrir un archivo que no recibiste demasiadas excepciones. Pero aún manejarías la excepción. Además, probablemente haya algo terriblemente incorrecto en el diseño de tu programa si estás escribiendo un método que hace esto.

21

Consejo rápido para cualquier otra persona que viene aquí con un problema similar:

Cuidado con aplicaciones de sincronización web, tales como DropBox. Acabo de pasar 2 horas pensando que la declaración "usar" (patrón de eliminación) está rota en .NET.

Finalmente me di cuenta de que Dropbox continuamente lee y escribe archivos en segundo plano, para sincronizarlos.

¿Adivina dónde se encuentra mi carpeta Proyectos de Visual Studio? Dentro de la carpeta "Mi Dropbox", por supuesto.

Por lo tanto, al ejecutar mi aplicación en modo Depuración, los archivos que estaba leyendo y escribiendo también fueron accedidos continuamente por DropBox para ser sincronizados con el servidor DropBox. Esto causó los conflictos de bloqueo/acceso.

Por lo menos ahora sé que necesito una función File Open más robusta (es decir, TryOpen() que hará múltiples intentos). Me sorprende que no sea una parte incorporada del marco.

[Actualización]

Aquí es mi función auxiliar:

/// <summary> 
/// Tries to open a file, with a user defined number of attempt and Sleep delay between attempts. 
/// </summary> 
/// <param name="filePath">The full file path to be opened</param> 
/// <param name="fileMode">Required file mode enum value(see MSDN documentation)</param> 
/// <param name="fileAccess">Required file access enum value(see MSDN documentation)</param> 
/// <param name="fileShare">Required file share enum value(see MSDN documentation)</param> 
/// <param name="maximumAttempts">The total number of attempts to make (multiply by attemptWaitMS for the maximum time the function with Try opening the file)</param> 
/// <param name="attemptWaitMS">The delay in Milliseconds between each attempt.</param> 
/// <returns>A valid FileStream object for the opened file, or null if the File could not be opened after the required attempts</returns> 
public FileStream TryOpen(string filePath, FileMode fileMode, FileAccess fileAccess,FileShare fileShare,int maximumAttempts,int attemptWaitMS) 
{ 
    FileStream fs = null; 
    int attempts = 0; 

    // Loop allow multiple attempts 
    while (true) 
    { 
     try 
     { 
      fs = File.Open(filePath, fileMode, fileAccess, fileShare); 

      //If we get here, the File.Open succeeded, so break out of the loop and return the FileStream 
      break; 
     } 
     catch (IOException ioEx) 
     { 
      // IOExcception is thrown if the file is in use by another process. 

      // Check the numbere of attempts to ensure no infinite loop 
      attempts++; 
      if (attempts > maximumAttempts) 
      { 
       // Too many attempts,cannot Open File, break and return null 
       fs = null; 
       break; 
      } 
      else 
      { 
       // Sleep before making another attempt 
       Thread.Sleep(attemptWaitMS); 

      } 

     } 

    } 
    // Reutn the filestream, may be valid or null 
    return fs; 
} 
+3

@Ash Creo que no leyó la pregunta correctamente ÉL quiere evitar intentar capturar. – Ravisha

+8

@ Ravisha, ¿has leído la respuesta más votado de Joel? Como dice Joel, ** "Lo que debes hacer es intentar abrir el archivo y manejar la excepción si falla" **. Por favor, no menosprecie solo porque no le gusta el hecho de que algo no se puede evitar. – Ash

+0

¡Gracias por el código! Una cosa, podría ser mejor usar usando, p. ver [la respuesta de Tazeem aquí] (http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/e99a7cea-43d3-49b1-82bc-5669e0b9d052) – Cel

-3
public static FileStream GetFileStream(String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, ref int attempts, int attemptWaitInMilliseconds) 
{    
    try 
    { 
     return File.Open(filePath, fileMode, fileAccess, fileShare); 
    } 
    catch (UnauthorizedAccessException unauthorizedAccessException) 
    { 
     if (attempts <= 0) 
     { 
      throw unauthorizedAccessException; 
     } 
     else 
     { 
      Thread.Sleep(attemptWaitInMilliseconds); 
      attempts--; 
      return GetFileStream(filePath, fileMode, fileAccess, fileShare, ref attempts, attemptWaitInMilliseconds); 
     } 
    } 
} 
+8

-1: use "throw"; no "throw desautorizadaAccessException;". Estás perdiendo tu rastro de pila. –

+0

¿Por qué se pasan los "intentos" por ref? Eso no tiene sentido. Tampoco prueba para '<=' en lugar de simplemente '=='. –

+1

@John: bueno, en este caso es * deseable * perder el rastro de pila (profundamente anidado) de la llamada recursiva, así que creo que en este caso 'throw ex' es en realidad * lo correcto * por hacer. –

3

Aquí está la solución que busca

var fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read, 
              System.Security.AccessControl.AccessControlActions.View, 
              MyPath); 

if (fileIOPermission.AllFiles == FileIOPermissionAccess.Read) 
{ 
    // Do your thing here... 
} 

esto crea un nuevo permiso de lectura basado en ver la ruta de todos los archivos y luego verifica si es igual a la lectura de acceso a archivos.

+0

Este código da falsos negativos en los directorios – Conrad

Cuestiones relacionadas