2011-01-13 15 views
25

Tengo un programa que se ejecuta como un servicio de Windows; procesa archivos en una carpeta específica. Como se trata de un servicio, supervisa constantemente una carpeta en busca de nuevos archivos que se han agregado. Parte del trabajo del programa es realizar comparaciones de archivos en la carpeta de destino y marcar los archivos que no coinciden. Lo que me gustaría hacer es poder detectar si una operación de copia está en progreso y cuándo se ha completado, de modo que un archivo no se marque de forma prematura si todavía no se ha copiado el archivo correspondiente en la carpeta de destino.C# - Esperando que se complete una operación de copia

Lo que estaba pensando hacer fue utilizar FileSystemWatcher para ver la carpeta de destino y ver si se está produciendo una operación de copia. Si lo hay, pongo el hilo principal de mi programa en modo de suspensión hasta que la operación de copia se haya completado, luego procedo a realizar la operación en la carpeta como siempre. Solo quería obtener una idea de este enfoque y ver si es válido; si alguien más tiene otros enfoques únicos para este problema, sería muy apreciado.

ACTUALIZACIÓN:

Gracias a todos por sus sugerencias

Actualización 2:

Me disculpo por la confusión, cuando el directorio de destino que digo, me refiero a la carpeta de origen que contiene todos los archivos que quiero para procesar. Una parte de la función de mi programa es copiar la estructura de directorios del directorio de origen a un directorio de destino y copiar todos los archivos válidos a ese directorio de destino, preservando la estructura de directorios del directorio fuente original, es decir, un usuario puede copiar carpetas que contienen archivos al directorio de origen. Quiero evitar errores asegurando que si un nuevo conjunto de carpetas que contiene más subcarpetas y archivos se copia en el directorio de origen para su procesamiento, mi programa no comenzará a funcionar en el directorio de destino hasta que el proceso de copia se haya completado.

+0

+1. Esa es una muy buena pregunta. Aún tengo que pensar en un enfoque que no parezca un truco. – David

+1

Esta pregunta es similar y tiene algunas buenas respuestas: http://stackoverflow.com/questions/30074/monitoring-files-how-to-know-when-a-file-is-complete – mfdoran

Respuesta

2

Lo que está buscando es un escenario típico de producer/consumer. Lo que debe hacer se describe en la sección 'Producer/consumer queue' en este page. Esto le permitirá usar varios subprocesos (tal vez abarcar un backgroundworker) para copiar archivos para que no bloquee el hilo principal de servicio de los eventos del sistema. & puede realizar tareas más significativas allí - como buscar nuevos archivos & actualizar la cola . Entonces, on main thread do check for new files en background threads perform the actual coping task. Desde la experiencia personal (han implementado estas tareas) no hay demasiada ganancia de rendimiento de este enfoque a menos que esté ejecutando en múltiples CPU pero el proceso es muy limpio & smooth + el código está lógicamente separado muy bien.

En resumen, lo que tiene que hacer es tener un objeto como el siguiente:

public class File 
{ 
    public string FullPath {get; internal set;} 
    public bool CopyInProgress {get; set;} // property to make sure 
    // .. other properties if desired 
} 

A continuación, siguiendo el tutorial publicado anteriormente cuestión un bloqueo en el objeto File & la cola para actualizarlo & copiarlo. Con este enfoque, puede usar este type approaches en lugar de supervisar constantemente la finalización de la copia de archivo. El punto importante a tener en cuenta aquí es que su servicio tiene solo una instancia de objeto de archivo por archivo físico real - solo asegúrese de (1) bloquear la cola al agregar & eliminar & (2) bloquear el objeto de archivo real al inicializar una actualización .

EDIT: Por encima de donde digo "no hay demasiada ganancia de rendimiento de este método a menos que" Me REFERE que si lo hace este enfoque en un solo hilo comparar a @ Jason de lo que sugiere este enfoque debe ser notablemente más rápido debido a La solución de @ Jason realiza operaciones de IO muy costosas que fallarán en la mayoría de los casos. Esto no lo he probado pero estoy bastante seguro ya que mi enfoque no requiere que las operaciones IO se abran (una sola vez), solo una vez (stream) & archivo cerrado (solo una vez). El enfoque @Jason sugiere múltiples operaciones abiertas, abiertas, abiertas y abiertas que fallarán excepto la última.

+0

Esto es mucho lo que quiero lograr, necesito evitar que mi programa haga algo con los archivos/carpetas en el directorio de origen mientras hay una copia Progreso. – kingrichard2005

10

Sí, use un FileSystemWatcher pero en lugar de mirar para ver el evento creado, mire para el evento cambiado. Después de cada activador, intente abrir el archivo. Algo como esto:

var watcher = new FileSystemWatcher(path, filter); 
watcher.Changed += (sender, e) => { 
    FileStream file = null; 
    try { 
     Thread.Sleep(100); // hack for timing issues 
     file = File.Open(
      e.FullPath, 
      FileMode.Open, 
      FileAccess.Read, 
      FileShare.Read 
     ); 
    } 
    catch(IOException) { 
     // we couldn't open the file 
     // this is probably because the copy operation is not done 
     // just swallow the exception 
     return; 
    } 

    // now we have a handle to the file 
}; 

Esto es lo mejor que puedes hacer, desafortunadamente. No hay una forma clara de saber que el archivo está listo para su uso.

+0

Esto es más o menos como yo ' lo he manejado también. Siempre se sintió "Hack-ish" para mí, pero como +1 porque no tengo nada mejor. – David

+1

@ivo s: ineficiente? ¿Costoso? ¿A quien le importa? Está sucediendo en un hilo de fondo y es tan increíblemente poco probable que sea un cuello de botella. Ugh. Además, está asumiendo que tiene control sobre el proceso de copia. Tenga en cuenta que esto no fue originalmente en su pregunta. – jason

+2

Si se siente aventurero, puede evitar la sobrecarga de tener que atrapar las excepciones utilizando una llamada p/invoke para tratar de abrir el archivo en lugar de la llamada contenedora .NET. La llamada WIN32 subyacente simplemente devuelve un identificador nulo para las fallas (debe ordenar la llamada usando SafeFileHandle de .Net para su correcto manejo). Cuando finalmente la llamada tenga éxito, puede crear un objeto FileStream desde SafeFileHandle. Escriba su propio contenedor para la llamada p/invoke para devolver FileStream (o null en caso de error) y evite la necesidad de try/catch. –

2

Un enfoque es intentar abrir el archivo y ver si obtiene un error. El archivo se bloqueará si se está copiando. Esto abrirá el archivo en modo compartido por lo que entrará en conflicto con un bloqueo de escritura ya está abierto en el archivo:

using(System.IO.File.Open("file", FileMode.Open,FileAccess.Read, FileShare.Read)) {} 

Otra es la de comprobar el tamaño del archivo. Cambiaría con el tiempo si el archivo se está copiando.

También es posible obtener una lista de todas las aplicaciones que han abierto un determinado archivo, pero no conozco la API para esto.

1

Sé que esta es una vieja pregunta, pero aquí hay una respuesta que hice girar después de buscar una respuesta a este problema. Esto tuvo que modificarse mucho para eliminar parte de la propiedad de lo que estaba trabajando, por lo que puede que no se compile directamente, pero te dará una idea. Esto funciona muy bien para mí:



void BlockingFileCopySync(FileInfo original, FileInfo copyPath) 
{ 
    bool ready = false; 

    FileSystemWatcher watcher = new FileSystemWatcher(); 
    watcher.NotifyFilter = NotifyFilters.LastWrite; 
    watcher.Path = copyPath.Directory.FullName; 
    watcher.Filter = "*" + copyPath.Extension; 
    watcher.EnableRaisingEvents = true; 

    bool fileReady = false; 
    bool firsttime = true; 
    DateTime previousLastWriteTime = new DateTime(); 

    // modify this as you think you need to... 
    int waitTimeMs = 100; 

    watcher.Changed += (sender, e) => 
    { 
     // Get the time the file was modified 
     // Check it again in 100 ms 
     // When it has gone a while without modification, it's done. 
     while (!fileReady) 
     { 
      // We need to initialize for the "first time", 
      // ie. when the file was just created. 
      // (Really, this could probably be initialized off the 
      // time of the copy now that I'm thinking of it.) 
      if (firsttime) 
      { 
       previousLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 
       firsttime = false; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 

      DateTime currentLastWriteTime = System.IO.File.GetLastWriteTime(copyPath.FullName); 

      bool fileModified = (currentLastWriteTime != previousLastWriteTime); 

      if (fileModified) 
      { 
       previousLastWriteTime = currentLastWriteTime; 
       System.Threading.Thread.Sleep(waitTimeMs); 
       continue; 
      } 
      else 
      { 
       fileReady = true; 
       break; 
      } 
     } 
    }; 

    System.IO.File.Copy(original.FullName, copyPath.FullName, true); 

    // This guy here chills out until the filesystemwatcher 
    // tells him the file isn't being writen to anymore. 
    while (!fileReady) 
    { 
     System.Threading.Thread.Sleep(waitTimeMs); 
    } 
} 

+0

Lamentablemente, esta solución no funciona para mí. GetLastWriteTime no se actualiza mientras se copia el archivo. Estoy usando Windows 7, tal vez el comportamiento es diferente en Windows Server, no estoy seguro. – KyleLib

+0

@KyleLib - ¿De verdad? Interesante, estaba usando Windows 7 también. Curioso. No estoy seguro de qué hacer con eso. Puede que tenga que romper esto y meterme con eso un poco. – trycatch

Cuestiones relacionadas