Tutorial :Wait until file is unlocked in .NET


What's the simplest way of blocking a thread until a file has been unlocked and is accessible for reading and renaming? For example, is there a WaitOnFile() somewhere in the .NET Framework?

I have a service that uses a FileSystemWatcher to look for files that are to be transmitted to an FTP site, but the file created event fires before the other process has finished writing the file.

The ideal solution would have a timeout period so the thread doesn't hang forever before giving up.

Edit: After trying out some of the solutions below, I ended up changing the system so that all files wrote to Path.GetTempFileName(), then performed a File.Move() to the final location. As soon as the FileSystemWatcher event fired, the file was already complete.


This was the answer I gave on a related question:

    /// <summary>      /// Blocks until the file is not locked any more.      /// </summary>      /// <param name="fullPath"></param>      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;      }  


Starting from Eric's answer, I included some improvements to make the code far more compact and reusable. Hope it's useful.

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;  }  


Here is a generic code to do this, independant from the file operation itself. This is an example on how to use it:

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


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

You can also define the retry count, and the wait time between retries.

NOTE: Unfortunately, the underlying Win32 error (ERROR_SHARING_VIOLATION) is not exposed with .NET, so I have added a small hack function (IsSharingViolation) based on reflection mechanisms to check this.

    /// <summary>      /// Wraps sharing violations that could occur on a file IO operation.      /// </summary>      /// <param name="action">The action to execute. May not be null.</param>      public static void WrapSharingViolations(WrapSharingViolationsCallback action)      {          WrapSharingViolations(action, null, 10, 100);      }        /// <summary>      /// Wraps sharing violations that could occur on a file IO operation.      /// </summary>      /// <param name="action">The action to execute. May not be null.</param>      /// <param name="exceptionsCallback">The exceptions callback. May be null.</param>      /// <param name="retryCount">The retry count.</param>      /// <param name="waitTime">The wait time in milliseconds.</param>      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;                  }              }          }      }        /// <summary>      /// Defines a sharing violation wrapper delegate.      /// </summary>      public delegate void WrapSharingViolationsCallback();        /// <summary>      /// Defines a sharing violation wrapper delegate for handling exception.      /// </summary>      public delegate bool WrapSharingViolationsExceptionsCallback(IOException ioe, int retry, int retryCount, int waitTime);        /// <summary>      /// Determines whether the specified exception is a sharing violation exception.      /// </summary>      /// <param name="exception">The exception. May not be null.</param>      /// <returns>      ///     <c>true</c> if the specified exception is a sharing violation exception; otherwise, <c>false</c>.      /// </returns>      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        }        /// <summary>      /// Gets the HRESULT of the specified exception.      /// </summary>      /// <param name="exception">The exception to test. May not be null.</param>      /// <param name="defaultValue">The default value in case of an error.</param>      /// <returns>The HRESULT value.</returns>      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;      }  


I threw together a helper class for these sorts of things. It will work if you have control over everything that would access the file. If you're expecting contention from a bunch of other things, then this is pretty worthless.

using System;  using System.IO;  using System.Threading;    /// <summary>  /// 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).  /// </summary>  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;      

Next Post »