等待文件锁释放的正确模式是什么?

我需要打开一个文件,但如果它目前不可用,我需要等到它准备好了。 什么是最好的方法?

情景

我正在使用文件作为应用程序数据的持久缓存机制。 这些数据需要经常读取和反序列化(只写一次,偶尔删除)。 我有一个清理过程,它运行在一个单独的线程上,确定不再需要哪些文件并删除它们。 打开和读取文件可能同时发生(很少,但可能发生),我希望进程等待并尝试再次读取数据。

谢谢!

我不是try / catch IOException的忠实粉丝,因为:

  1. exception的原因未知。
  2. 我不喜欢“预期的”exception,因为我经常在exception时运行。

您可以通过调用CreateFile并在/最终返回句柄时返回流来无exception地执行此操作:

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 

csharptest.net方法的更通用版本看起来像这样(同样,使用SafeFileHandle并删除超时抛出的exception,你可以在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); 

这取决于谁控制文件。 如果应用程序的某个部分需要等到应用程序的另一部分完成准备文件,那么您可以使用ManualResetEvent 。 也就是说,在启动时,您的程序会创建一个新事件:

public ManualResetEvent FileEvent = new ManualResetEvent(false);

现在,正在等待该文件的程序部分具有以下代码:

FileEvent.WaitOne();

当文件准备就绪时,创建文件的程序部分会执行此操作:

FileEvent.Set();

如果您的应用程序必须等待另一个您无法控制的应用程序正在使用的文件,那么您唯一真正的解决方案就是不断尝试打开该文件。

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

当然,您可能不希望无条件地捕获IOException 。 您可能希望捕获您知道如何处理的特定exception(例如,如果您有DirectoryNotFoundException ,则不希望再次尝试)。 I / O函数记录了它们应该抛出的exception,以及在什么情况下。

像所有“最好的方法”问题一样,这个问题取决于您的需求。 一些容易想到的选项:

  1. 中止尝试
  2. 循环直到文件解锁
  3. 询问用户该怎么做

你选择哪一个取决于你如何处理它。