Espere hasta que el archivo esté desbloqueado en .NET

¿Cuál es la forma más simple de bloquear un hilo hasta que se haya desbloqueado un archivo y esté accesible para leer y cambiar el nombre? Por ejemplo, ¿hay un WaitOnFile () en algún lugar de .NET Framework?

Tengo un servicio que usa un FileSystemWatcher para buscar archivos que se van a transmitir a un sitio FTP, pero el evento creado del archivo se dispara antes de que el otro proceso haya terminado de escribir el archivo.

La solución ideal tendría un período de tiempo de espera para que el hilo no se cuelgue para siempre antes de darse por vencido.

Editar: Después de probar algunas de las soluciones a continuación, terminé cambiando el sistema para que todos los archivos escribieran a Path.GetTempFileName() , luego realizara un File.Move() a la ubicación final. Tan pronto como el evento FileSystemWatcher se disparó, el archivo ya estaba completo.

Esta fue la respuesta que di en una pregunta relacionada :

  ///  /// Blocks until the file is not locked any more. ///  ///  bool WaitForFile(string fullPath) { int numTries = 0; while (true) { ++numTries; try { // Attempt to open the file exclusively. using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 100)) { fs.ReadByte(); // If we got this far the file is ready break; } } catch (Exception ex) { Log.LogWarning( "WaitForFile {0} failed to get an exclusive lock: {1}", fullPath, ex.ToString()); if (numTries > 10) { Log.LogWarning( "WaitForFile {0} giving up after 10 tries", fullPath); return false; } // Wait for the lock to be released System.Threading.Thread.Sleep(500); } } Log.LogTrace("WaitForFile {0} returning true after {1} tries", fullPath, numTries); return true; } 

A partir de la respuesta de Eric, incluí algunas mejoras para hacer que el código sea mucho más compacto y reutilizable. Espero que sea útil.

 FileStream WaitForFile (string fullPath, FileMode mode, FileAccess access, FileShare share) { for (int numTries = 0; numTries < 10; numTries++) { FileStream fs = null; try { fs = new FileStream (fullPath, mode, access, share); return fs; } catch (IOException) { if (fs != null) { fs.Dispose (); } Thread.Sleep (50); } } return null; } 

Aquí hay un código genérico para hacer esto, independientemente de la operación del archivo en sí. Este es un ejemplo sobre cómo usarlo:

 WrapSharingViolations(() => File.Delete(myFile)); 

o

 WrapSharingViolations(() => File.Copy(mySourceFile, myDestFile)); 

También puede definir el recuento de rebashs y el tiempo de espera entre rebashs.

NOTA: Desafortunadamente, el error Win32 subyacente (ERROR_SHARING_VIOLATION) no está expuesto con .NET, por lo que he agregado una pequeña función de IsSharingViolation ( IsSharingViolation ) basada en mecanismos de reflexión para verificar esto.

  ///  /// Wraps sharing violations that could occur on a file IO operation. ///  /// The action to execute. May not be null. public static void WrapSharingViolations(WrapSharingViolationsCallback action) { WrapSharingViolations(action, null, 10, 100); } ///  /// Wraps sharing violations that could occur on a file IO operation. ///  /// The action to execute. May not be null. /// The exceptions callback. May be null. /// The retry count. /// The wait time in milliseconds. public static void WrapSharingViolations(WrapSharingViolationsCallback action, WrapSharingViolationsExceptionsCallback exceptionsCallback, int retryCount, int waitTime) { if (action == null) throw new ArgumentNullException("action"); for (int i = 0; i < retryCount; i++) { try { action(); return; } catch (IOException ioe) { if ((IsSharingViolation(ioe)) && (i < (retryCount - 1))) { bool wait = true; if (exceptionsCallback != null) { wait = exceptionsCallback(ioe, i, retryCount, waitTime); } if (wait) { System.Threading.Thread.Sleep(waitTime); } } else { throw; } } } } ///  /// Defines a sharing violation wrapper delegate. ///  public delegate void WrapSharingViolationsCallback(); ///  /// Defines a sharing violation wrapper delegate for handling exception. ///  public delegate bool WrapSharingViolationsExceptionsCallback(IOException ioe, int retry, int retryCount, int waitTime); ///  /// Determines whether the specified exception is a sharing violation exception. ///  /// The exception. May not be null. ///  /// true if the specified exception is a sharing violation exception; otherwise, false. ///  public static bool IsSharingViolation(IOException exception) { if (exception == null) throw new ArgumentNullException("exception"); int hr = GetHResult(exception, 0); return (hr == -2147024864); // 0x80070020 ERROR_SHARING_VIOLATION } ///  /// Gets the HRESULT of the specified exception. ///  /// The exception to test. May not be null. /// The default value in case of an error. /// The HRESULT value. public static int GetHResult(IOException exception, int defaultValue) { if (exception == null) throw new ArgumentNullException("exception"); try { const string name = "HResult"; PropertyInfo pi = exception.GetType().GetProperty(name, BindingFlags.NonPublic | BindingFlags.Instance); // CLR2 if (pi == null) { pi = exception.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance); // CLR4 } if (pi != null) return (int)pi.GetValue(exception, null); } catch { } return defaultValue; } 

Hice una clase de ayuda para este tipo de cosas. Funcionará si tiene control sobre todo lo que accedería al archivo. Si esperas contienda por muchas otras cosas, entonces esto no tiene ningún valor.

 using System; using System.IO; using System.Threading; ///  /// This is a wrapper aroung a FileStream. While it is not a Stream itself, it can be cast to /// one (keep in mind that this might throw an exception). ///  public class SafeFileStream: IDisposable { #region Private Members private Mutex m_mutex; private Stream m_stream; private string m_path; private FileMode m_fileMode; private FileAccess m_fileAccess; private FileShare m_fileShare; #endregion//Private Members #region Constructors public SafeFileStream(string path, FileMode mode, FileAccess access, FileShare share) { m_mutex = new Mutex(false, String.Format("Global\\{0}", path.Replace('\\', '/'))); m_path = path; m_fileMode = mode; m_fileAccess = access; m_fileShare = share; } #endregion//Constructors #region Properties public Stream UnderlyingStream { get { if (!IsOpen) throw new InvalidOperationException("The underlying stream does not exist - try opening this stream."); return m_stream; } } public bool IsOpen { get { return m_stream != null; } } #endregion//Properties #region Functions ///  /// Opens the stream when it is not locked. If the file is locked, then ///  public void Open() { if (m_stream != null) throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage); m_mutex.WaitOne(); m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare); } public bool TryOpen(TimeSpan span) { if (m_stream != null) throw new InvalidOperationException(SafeFileResources.FileOpenExceptionMessage); if (m_mutex.WaitOne(span)) { m_stream = File.Open(m_path, m_fileMode, m_fileAccess, m_fileShare); return true; } else return false; } public void Close() { if (m_stream != null) { m_stream.Close(); m_stream = null; m_mutex.ReleaseMutex(); } } public void Dispose() { Close(); GC.SuppressFinalize(this); } public static explicit operator Stream(SafeFileStream sfs) { return sfs.UnderlyingStream; } #endregion//Functions } 

Funciona usando un mutex con nombre. Quienes deseen acceder al archivo intentan adquirir el control del mutex nombrado, que comparte el nombre del archivo (con los ‘\’ convertidos en ‘/’ s). Puede usar Open (), que se detendrá hasta que se pueda acceder al mutex o puede usar TryOpen (TimeSpan), que intenta adquirir el mutex para la duración dada y devuelve falso si no puede adquirirlo dentro del lapso de tiempo. Lo más probable es que esto se use dentro de un bloque de uso, para garantizar que los lockings se liberen adecuadamente y que la stream (si está abierta) se elimine de forma adecuada cuando se deseche este objeto.

Hice una prueba rápida con ~ 20 cosas para hacer varias lecturas / escrituras del archivo y no vi corrupción. Obviamente no es muy avanzado, pero debería funcionar para la mayoría de los casos simples.

Para esta aplicación en particular, la observación directa del archivo conducirá inevitablemente a un error difícil de rastrear, especialmente cuando el tamaño del archivo aumenta. Aquí hay dos estrategias diferentes que funcionarán.

  • Ftp dos archivos pero solo mira uno. Por ejemplo, envíe los archivos important.txt e important.finish. Solo mira el archivo de finalización pero procesa el txt.
  • FTP un archivo, pero renómbrelo cuando haya terminado. Por ejemplo, envíe important.wait y haga que el remitente lo cambie a important.txt cuando haya terminado.

¡Buena suerte!

Una de las técnicas que utilicé hace algún tiempo fue escribir mi propia función. Básicamente capte la excepción y vuelva a intentar con un temporizador que puede disparar durante un tiempo especificado. Si hay una mejor manera, por favor comparte.

Desde MSDN :

El evento OnCreated se genera tan pronto como se crea un archivo. Si un archivo se está copiando o transfiriendo a un directorio vigilado, el evento OnCreated se generará inmediatamente, seguido de uno o más eventos OnChanged.

Su FileSystemWatcher podría modificarse para que no haga su lectura / cambio de nombre durante el evento “OnCreated”, sino más bien:

  1. Despliega un hilo que sondea el estado del archivo hasta que no está bloqueado (utilizando un objeto FileInfo)
  2. Llama al servicio para procesar el archivo tan pronto como determina que el archivo ya no está bloqueado y está listo para funcionar

No sé lo que está utilizando para determinar el estado de locking del archivo, pero algo como esto debería hacerlo.

 mientras (verdadero)
 {
     tratar {
         stream = File.Open (fileName, fileMode);
         descanso;
     }
     catch (FileIOException) {

         // verifica si se trata de un problema de locking

         Thread.Sleep (100);
     }
 }

En la mayoría de los casos, un enfoque simple como @harpo sugiere que funcionará. Puede desarrollar código más sofisticado utilizando este enfoque:

  • Encuentre todos los identificadores abiertos para el archivo seleccionado usando SystemHandleInformation \ SystemProcessInformation
  • Clase de WaitLandle de subclase para obtener acceso a su identificador interno
  • Pase identificadores encontrados envueltos en WaitHandle subclase a WaitHandle.WaitAny método

Anuncio del archivo de activación del proceso de transferencia SameNameASTrasferedFile.trg que se crea después de que se completa la transmisión del archivo.

Luego configure FileSystemWatcher que disparará el evento solo en el archivo * .trg.

Una solución posible sería combinar un sistema de archivos con varias encuestas.

reciba un aviso de cada cambio en un archivo, y cuando reciba una notificación, compruebe si está bloqueado como se indica en la respuesta actualmente aceptada: https://stackoverflow.com/a/50800/6754146 El código para abrir el filestream se copia de la respuesta y ligeramente modificado:

 public static void CheckFileLock(string directory, string filename, Func callBack) { var watcher = new FileSystemWatcher(directory, filename); FileSystemEventHandler check = async (sender, eArgs) => { string fullPath = Path.Combine(directory, filename); try { // Attempt to open the file exclusively. using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 100)) { fs.ReadByte(); watcher.EnableRaisingEvents = false; // If we got this far the file is ready } watcher.Dispose(); await callBack(); } catch (IOException) { } }; watcher.NotifyFilter = NotifyFilters.LastWrite; watcher.IncludeSubdirectories = false; watcher.EnableRaisingEvents = true; //Attach the checking to the changed method, //on every change it gets checked once watcher.Changed += check; //Initially do a check for the case it is already released check(null, null); } 

De esta forma, puedes buscar un archivo si está bloqueado y recibir una notificación cuando se cierra sobre la callback especificada, de esta manera evitas las encuestas excesivamente agresivas y solo haces el trabajo cuando en realidad se puede cerrar.

Lo hago de la misma manera que Gulzar, solo sigo intentando con un bucle.

De hecho, ni siquiera me molesto con el observador del sistema de archivos. Sondear una unidad de red en busca de archivos nuevos una vez por minuto es barato.

Simplemente use el evento Modificado con NotifyFilter NotifyFilters.LastWrite :

 var watcher = new FileSystemWatcher { Path = @"c:\temp\test", Filter = "*.xml", NotifyFilter = NotifyFilters.LastWrite }; watcher.Changed += watcher_Changed; watcher.EnableRaisingEvents = true; 

Me encontré con un problema similar al agregar un archivo adjunto de Outlook. “Usar” salvó el día.

 string fileName = MessagingBLL.BuildPropertyAttachmentFileName(currProp); //create a temporary file to send as the attachment string pathString = Path.Combine(Path.GetTempPath(), fileName); //dirty trick to make sure locks are released on the file. using (System.IO.File.Create(pathString)) { } mailItem.Subject = MessagingBLL.PropertyAttachmentSubject; mailItem.Attachments.Add(pathString, Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing); 

¿Qué tal esto como una opción?

 private void WaitOnFile(string fileName) { FileInfo fileInfo = new FileInfo(fileName); for (long size = -1; size != fileInfo.Length; fileInfo.Refresh()) { size = fileInfo.Length; System.Threading.Thread.Sleep(1000); } } 

Por supuesto, si el tamaño del archivo está preasignado en la creación, obtendría un falso positivo.