What is the correct pattern to wait for a file lock to be released?

I need to open the file, but if it is currently unavailable, I need to wait until it is ready. What is the best approach?

SCENARIO

I use files as a mechanism for constantly caching application data. This data needs to be read and deserialized often (write only once and sometimes delete). I have a cleanup process that runs in a separate thread that determines which files are no longer needed and deletes them. Opening and reading files can happen at the same time (rarely, but it can happen), and I want the process to wait and try to read the data again.

Thanks!

+6
c # locking
source share
4 answers

I am not a big fan of IOException try / catch, because:

  • The reason for the exclusion is unknown.
  • I don't like the “expected” exceptions, as I often run with a break for excpetion.

You can do this without exception by calling CreateFile and returning the stream when / if it finally returns the handle:

public static System.IO.Stream WaitForExclusiveFileAccess(string filePath, int timeout) { IntPtr fHandle; int errorCode; DateTime start = DateTime.Now; while(true) { fHandle = CreateFile(filePath, EFileAccess.GenericRead | EFileAccess.GenericWrite, EFileShare.None, IntPtr.Zero, ECreationDisposition.OpenExisting, EFileAttributes.Normal, IntPtr.Zero); if (fHandle != IntPtr.Zero && fHandle.ToInt64() != -1L) return new System.IO.FileStream(fHandle, System.IO.FileAccess.ReadWrite, true); errorCode = Marshal.GetLastWin32Error(); if (errorCode != ERROR_SHARING_VIOLATION) break; if (timeout >= 0 && (DateTime.Now - start).TotalMilliseconds > timeout) break; System.Threading.Thread.Sleep(100); } throw new System.IO.IOException(new System.ComponentModel.Win32Exception(errorCode).Message, errorCode); } #region Win32 const int ERROR_SHARING_VIOLATION = 32; [Flags] enum EFileAccess : uint { GenericRead = 0x80000000, GenericWrite = 0x40000000 } [Flags] enum EFileShare : uint { None = 0x00000000, } enum ECreationDisposition : uint { OpenExisting = 3, } [Flags] enum EFileAttributes : uint { Normal = 0x00000080, } [DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)] static extern IntPtr CreateFile( string lpFileName, EFileAccess dwDesiredAccess, EFileShare dwShareMode, IntPtr lpSecurityAttributes, ECreationDisposition dwCreationDisposition, EFileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile); #endregion 
+9
source share

It depends on who controls the file. If one part of the application needs to wait for the other part of the application to finish preparing the file, you can use ManualResetEvent . That is, at startup, your program creates a new event:

public ManualResetEvent FileEvent = new ManualResetEvent(false);

Now the part of the program that is waiting for the file has this code:

FileEvent.WaitOne();

And the part of the program that creates the file does this when the file is ready:

FileEvent.Set();

If your application has to wait for a file that is being used by another application that you do not control, the only real solution is to constantly try to open the file.

 FileStream f = null; while (f == null) { try { f = new FileStream(...); } catch (IOException) { // wait a bit and try again Thread.Sleep(5000); } } 

Of course, you probably won't want to unconditionally catch an IOException . You will probably want to catch certain exceptions that you know how to handle (for example, you won’t want to try again if you have a DirectoryNotFoundException ). The I / O functions document which exceptions they should throw and under what circumstances.

+2
source share

A more general version of the csharptest.net method might look like this (also, using SafeFileHandle and a remote exception exception during a timeout, you can get the enumeration values ​​at http://www.pinvoke.net/default.aspx/kernel32.createfile ):

  public static FileStream WaitForFileAccess(string filePath, FileMode fileMode, FileAccess access, FileShare share, TimeSpan timeout) { int errorCode; DateTime start = DateTime.Now; while (true) { SafeFileHandle fileHandle = CreateFile(filePath, ConvertFileAccess(access), ConvertFileShare(share), IntPtr.Zero, ConvertFileMode(fileMode), EFileAttributes.Normal, IntPtr.Zero); if (!fileHandle.IsInvalid) { return new FileStream(fileHandle, access); } errorCode = Marshal.GetLastWin32Error(); if (errorCode != ERROR_SHARING_VIOLATION) { break; } if ((DateTime.Now - start) > timeout) { return null; // timeout isn't an exception } Thread.Sleep(100); } throw new IOException(new Win32Exception(errorCode).Message, errorCode); } private static EFileAccess ConvertFileAccess(FileAccess access) { return access == FileAccess.ReadWrite ? EFileAccess.GenericRead | EFileAccess.GenericWrite : access == FileAccess.Read ? EFileAccess.GenericRead : EFileAccess.GenericWrite; } private static EFileShare ConvertFileShare(FileShare share) { return (EFileShare) ((uint) share); } private static ECreationDisposition ConvertFileMode(FileMode mode) { return mode == FileMode.Open ? ECreationDisposition.OpenExisting : mode == FileMode.OpenOrCreate ? ECreationDisposition.OpenAlways : (ECreationDisposition) (uint) mode; } [DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)] private static extern SafeFileHandle CreateFile( string lpFileName, EFileAccess dwDesiredAccess, EFileShare dwShareMode, IntPtr lpSecurityAttributes, ECreationDisposition dwCreationDisposition, EFileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile); 
+2
source share

Like all of the questions “what works best,” it depends on your needs. Some options that easily come to mind:

  • Abort attempt
  • Loop until the file is unlocked.
  • Ask the user what to do about it.

Which one you choose depends on how you can deal with it.

0
source share

All Articles