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

时间:2010-09-27 22:49:18

标签: c# .net locking

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

SCENARIO

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

谢谢!

4 个答案:

答案 0 :(得分:9)

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

  1. 异常的原因未知。
  2. 我不喜欢“预期的”异常,因为我经常在例外情况下使用break。
  3. 您可以通过调用CreateFile并在/最终返回句柄时返回流来无异常地执行此操作:

    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
    

答案 1 :(得分:4)

csharptest.net方法的更通用版本可能如下所示(使用SafeFileHandle并删除超时抛出异常,您可以在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 :(得分:2)

这取决于谁控制文件。如果应用程序的某个部分需要等到应用程序的另一部分完成准备文件,那么您可以使用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。您可能希望捕获您知道如何处理的特定异常(例如,如果您有DirectoryNotFoundException,则不希望再次尝试)。 I / O函数记录了它们应该抛出的异常,以及在什么情况下。

答案 3 :(得分:0)

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

  1. 中止尝试
  2. 循环直到文件解锁
  3. 询问用户该怎么做
  4. 您选择哪一个取决于您如何处理它。