2012-01-22 8 views
24

Estoy trabajando en un software de servidor que periódicamente necesita guardar datos en el disco. Necesito asegurarme de que el archivo anterior se sobrescribe y que el archivo no se corrompe (por ejemplo, solo se sobrescribe parcialmente) en caso de circunstancias inesperadas.Almacenamiento de archivos confiable (File.Replace) en un entorno ocupado

He adoptado el siguiente patrón:

string tempFileName = Path.GetTempFileName(); 
// ...write out the data to temporary file... 
MoveOrReplaceFile(tempFileName, fileName); 

... donde MoveOrReplaceFile es:

public static void MoveOrReplaceFile(string source, string destination) { 
    if (source == null) throw new ArgumentNullException("source"); 
    if (destination == null) throw new ArgumentNullException("destination"); 
    if (File.Exists(destination)) { 
     // File.Replace does not work across volumes 
     if (Path.GetPathRoot(Path.GetFullPath(source)) == Path.GetPathRoot(Path.GetFullPath(destination))) { 
      File.Replace(source, destination, null, true); 
     } else { 
      File.Copy(source, destination, true); 
     } 
    } else { 
     File.Move(source, destination); 
    } 
} 

Esto funciona bien siempre y cuando el servidor tiene acceso exclusivo a los archivos. Sin embargo, File.Replace parece ser muy sensible al acceso externo a los archivos. En cualquier momento en mi software se ejecuta en un sistema con un antivirus o un sistema de copia de seguridad en tiempo real, los errores aleatorios File.Replace empiezan a saltar:

System.IO.IOException: No se puede eliminar el archivo se va a sustituir.

Estas son algunas de las posibles causas que he eliminado:

  • archivo inédito maneja: utilizando() asegura que todos los identificadores de archivo son liberados tan pronto como sea posible.
  • Problemas de subprocesamiento: lock() guarda todo el acceso a cada archivo.
  • Diferentes volúmenes de disco: File.Replace() falla cuando se usa en todos los volúmenes de disco. Mi método ya lo comprueba y vuelve a File.Copy().

Y aquí están algunas sugerencias que me he encontrado, y por qué prefiero no los uso:

  • Volume Shadow Copy Service: Esto sólo funciona siempre y cuando la tercera problemática El software de la parte (monitores de respaldo y antivirus, etc.) también usa VSS. El uso de VSS requiere toneladas de P/Invoke, y tiene problemas específicos de la plataforma.
  • Bloqueando archivos: En C#, bloquear un archivo requiere mantener un FileStream abierto. Esto mantendría fuera el software de terceros, pero 1) Todavía no podré reemplazar el archivo usando File.Replace, y 2) Como mencioné anteriormente, prefiero escribir en un archivo temporal primero, para evitar accidentes. corrupción.

Agradecería cualquier entrada para hacer que File.Replace funcione siempre o, más generalmente, guardar/sobrescribir archivos en el disco de manera confiable.

+1

¿Espera 'MoveOrReplaceFile' a estudiarse a la vez (lo que significa visitada por múltiples hilos de su aplicación * * o incluso de varias instancias de la aplicación)? –

+1

Mi propio código nunca llama a MoveOrReplaceFile al mismo tiempo, pero hay otros procesos fuera de mi control que pueden intentar leer el archivo. Esos accesos de lectura aleatoria son los que hacen que File.Replace falle. – matvei

Respuesta

26

You realmente desea utilizar el tercero parámetro, el nombre del archivo de copia de seguridad. Eso permite a Windows simplemente cambiar el nombre del archivo original sin tener que eliminarlo. La eliminación fallará si cualquier otro proceso tiene el archivo abierto sin eliminar el uso compartido, el cambio de nombre nunca es un problema. Luego puede eliminarlo usted mismo después de la llamada Reemplazar() e ignorar un error. También elimínelo antes de la llamada Reemplazar() para que el cambio de nombre no falle y la limpieza fallará en los intentos anteriores. Así que más o menos:

string backup = destination + ".bak"; 
File.Delete(backup); 
File.Replace(source, destination, backup, true); 
try { 
    File.Delete(backup); 
} 
catch { 
    // optional: 
    filesToDeleteLater.Add(backup); 
} 
+0

Hans, ¿podría mostrar el código para este patrón? Esto podría convertirse en la respuesta canónica para esta pregunta en el futuro. –

+0

¡Esto es precisamente lo que estaba buscando! Muchas gracias. – matvei

+2

¿Esto no solo reduce el problema un nivel? ¿Qué sucede si el archivo de copia de seguridad no puede eliminarse? – grantnz

1

Si el software está escribiendo en una partición NTFS, intente utilizar Transactional NTFS. Puede usar AlphFS para un contenedor .NET a la API. Esa es probablemente la forma más confiable de escribir archivos y prevenir la corrupción.

+0

Sin embargo, solo funciona en Vista y más adelante. – CodesInChaos

+1

'File.Replace()' en mono mágicamente hace cosas como usar POSIX 'rename()' en otros sistemas operativos. ¿No alejarse de las clases integradas complica la portabilidad? Además, si 'File.Replace()' en realidad funcionó como está documentado y confiablemente, ya proporciona la funcionalidad necesaria ... – binki

2

Existen varios enfoques posibles, aquí algunas de ellas:

  1. utilizar un archivo de "bloqueo" - un archivo temporal que se crea antes de la operación e indica otros escritores (o lectores) que el archivo está siendo modificado y, por lo tanto, exclusivamente bloqueado.Una vez completada la operación, elimine el archivo de bloqueo. Este método supone que el comando de creación de archivos es atómico.
  2. Utilice API transaccional NTFS (si corresponde).
  3. Cree un enlace al archivo, escriba el archivo modificado con un nombre aleatorio (por ejemplo, Guid.NewGuid()) y luego vuelva a asignar el vínculo al nuevo archivo. Todos los lectores accederán al archivo a través del enlace (cuyo nombre se conoce).

Por supuesto los 3 enfoques tienen sus propios inconvenientes y ventajas

Cuestiones relacionadas