如何检查文件锁?

ric*_*ree 246 .net c# io filelock

有没有办法检查文件是否被锁定而不使用try/catch块?

现在,我所知道的唯一方法就是打开文件并抓住任何文件System.IO.IOException.

Dix*_*onD 172

当我遇到类似的问题时,我完成了以下代码:

public bool IsFileLocked(string filePath)
{
    try
    {
        using (File.Open(filePath, FileMode.Open)){}
    }
    catch (IOException e)
    {
        var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);

        return errorCode == 32 || errorCode == 33;
    }

    return false;
}
Run Code Online (Sandbox Code Playgroud)

  • 如果在"return false"和你再次打开文件之间的某些东西抢走了它会怎么样?比赛条件啊! (4认同)
  • @kite:现在有更好的方法http://stackoverflow.com/a/20623302/141172 (2认同)
  • @RenniePet以下页面应该更有用:https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms681382%28v=vs.85%29.aspx相关错误是ERROR_SHARING_VIOLATION和ERROR_LOCK_VIOLATION (2认同)
  • 如果将结果与常量进行比较,那么在这里进行位掩码的目的是什么?此外,`GetHRForException` 有副作用,从 .NET 4.5 开始可以直接读取 `HResult`。 (2认同)
  • @BartoszKP 确实如此,谢谢你。以下是“catch”子句的更新内容:`const int ERROR_SHARING_VIOLATION = 0x20; 常量 int ERROR_LOCK_VIOLATION = 0x21; int errorCode = e.HResult &amp; 0x0000FFFF; 返回错误代码== ERROR_SHARING_VIOLATION || 错误代码== ERROR_LOCK_VIOLATION;` (2认同)

Eri*_* J. 138

其他答案依赖于旧信息.这个提供了更好的解决方案.

很久以前,无法可靠地获取锁定文件的进程列表,因为Windows根本没有跟踪该信息.为了支持Restart Manager API,现在可以跟踪该信息.从Windows Vista和Windows Server 2008(重新启动管理器:运行时要求)开始,可以使用Restart Manager API .

我把代码放在一起,它接受一个文件的路径并返回List<Process>锁定该文件的所有进程.

static public class FileUtil
{
    [StructLayout(LayoutKind.Sequential)]
    struct RM_UNIQUE_PROCESS
    {
        public int dwProcessId;
        public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
    }

    const int RmRebootReasonNone = 0;
    const int CCH_RM_MAX_APP_NAME = 255;
    const int CCH_RM_MAX_SVC_NAME = 63;

    enum RM_APP_TYPE
    {
        RmUnknownApp = 0,
        RmMainWindow = 1,
        RmOtherWindow = 2,
        RmService = 3,
        RmExplorer = 4,
        RmConsole = 5,
        RmCritical = 1000
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct RM_PROCESS_INFO
    {
        public RM_UNIQUE_PROCESS Process;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
        public string strAppName;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
        public string strServiceShortName;

        public RM_APP_TYPE ApplicationType;
        public uint AppStatus;
        public uint TSSessionId;
        [MarshalAs(UnmanagedType.Bool)]
        public bool bRestartable;
    }

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
    static extern int RmRegisterResources(uint pSessionHandle,
                                          UInt32 nFiles,
                                          string[] rgsFilenames,
                                          UInt32 nApplications,
                                          [In] RM_UNIQUE_PROCESS[] rgApplications,
                                          UInt32 nServices,
                                          string[] rgsServiceNames);

    [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
    static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);

    [DllImport("rstrtmgr.dll")]
    static extern int RmEndSession(uint pSessionHandle);

    [DllImport("rstrtmgr.dll")]
    static extern int RmGetList(uint dwSessionHandle,
                                out uint pnProcInfoNeeded,
                                ref uint pnProcInfo,
                                [In, Out] RM_PROCESS_INFO[] rgAffectedApps,
                                ref uint lpdwRebootReasons);

    /// <summary>
    /// Find out what process(es) have a lock on the specified file.
    /// </summary>
    /// <param name="path">Path of the file.</param>
    /// <returns>Processes locking the file</returns>
    /// <remarks>See also:
    /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
    /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
    /// 
    /// </remarks>
    static public List<Process> WhoIsLocking(string path)
    {
        uint handle;
        string key = Guid.NewGuid().ToString();
        List<Process> processes = new List<Process>();

        int res = RmStartSession(out handle, 0, key);

        if (res != 0)
            throw new Exception("Could not begin restart session.  Unable to determine file locker.");

        try
        {
            const int ERROR_MORE_DATA = 234;
            uint pnProcInfoNeeded = 0,
                 pnProcInfo = 0,
                 lpdwRebootReasons = RmRebootReasonNone;

            string[] resources = new string[] { path }; // Just checking on one resource.

            res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);

            if (res != 0) 
                throw new Exception("Could not register resource.");                                    

            //Note: there's a race condition here -- the first call to RmGetList() returns
            //      the total number of process. However, when we call RmGetList() again to get
            //      the actual processes this number may have increased.
            res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);

            if (res == ERROR_MORE_DATA)
            {
                // Create an array to store the process results
                RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
                pnProcInfo = pnProcInfoNeeded;

                // Get the list
                res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);

                if (res == 0)
                {
                    processes = new List<Process>((int)pnProcInfo);

                    // Enumerate all of the results and add them to the 
                    // list to be returned
                    for (int i = 0; i < pnProcInfo; i++)
                    {
                        try
                        {
                            processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
                        }
                        // catch the error -- in case the process is no longer running
                        catch (ArgumentException) { }
                    }
                }
                else
                    throw new Exception("Could not list processes locking resource.");                    
            }
            else if (res != 0)
                throw new Exception("Could not list processes locking resource. Failed to get size of result.");                    
        }
        finally
        {
            RmEndSession(handle);
        }

        return processes;
    }
}
Run Code Online (Sandbox Code Playgroud)

UPDATE

下面是另一个关于如何使用Restart Manager API的示例代码的讨论.

  • 这里唯一的答案实际上回答了OP问题......太好了! (14认同)
  • 我刚刚使用它,它确实可以在网络上运行. (7认同)
  • 如果文件位于网络共享上并且文件可能锁定在另一台PC上,这是否可行? (4认同)
  • @VadimLevkovsky哦对不起,这是一个工作链接:https://gist.github.com/mlaily/9423f1855bb176d52a327f5874915a97 (4认同)
  • 如果有人有兴趣,[我创建了一个要点](https://gist.github.com/yaurthek/9423f1855bb176d52a327f5874915a97)的灵感来自这个答案,但更简单,并使用msdn正确格式化的文档进行了改进.我还从Raymond Chen的文章中汲取灵感,并关注竞争条件.**BTW我注意到这个方法需要大约30ms来运行**(单独使用RmGetList方法需要20ms),**而DixonD的方法,尝试获取锁定,需要不到5ms ...**保持在介意你打算在紧密循环中使用它...... (3认同)

ang*_*son 131

不,不幸的是,如果你考虑一下,那么这些信息无论如何都会毫无价值,因为文件可能会在下一秒被锁定(读取:短时间跨度).

为什么你需要知道文件是否被锁定了?知道这可能会给我们一些其他方式给你很好的建议.

如果您的代码如下所示:

if not locked then
    open and update file
Run Code Online (Sandbox Code Playgroud)

然后在两行之间,另一个进程可以轻松锁定文件,为您提供与尝试避免开始时相同的问题:异常.

  • 如果文件被锁定,我们可以等一段时间再试一次.如果它是文件访问的另一种问题,那么我们应该只传播异常. (13认同)
  • 是的,但是单独检查文件是否被锁定是没用的,唯一正确的方法是尝试打开文件以达到您需要文件的目的,然后在此时处理锁定问题.然后,正如你所说,等待,或以另一种方式处理它. (13认同)
  • @ LasseV.Karlsen进行抢先检查的另一个好处是,您可以在尝试长时间操作和中途中断之前通知用户.当然仍然可以在中途进行锁定并且需要处理,但在许多情况下,这将有助于用户体验. (7认同)
  • 您可以为访问权争论相同,但它当然不太可能. (2认同)
  • 在很多情况下,锁定测试不会“无用”。检查 IIS 日志(每天锁定一个文件以进行写入)以查看哪个被锁定是此类日志记录情况的典型示例。可以很好地识别系统上下文以从锁定测试中获得价值。_"✗ 如果可能,请勿将异常用于正常的控制流。"_ — https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/exception-throwing (2认同)

小智 19

您还可以检查是否有任何进程正在使用此文件,并显示必须关闭的程序列表,以便像安装程序一样继续.

public static string GetFileProcessName(string filePath)
{
    Process[] procs = Process.GetProcesses();
    string fileName = Path.GetFileName(filePath);

    foreach (Process proc in procs)
    {
        if (proc.MainWindowHandle != new IntPtr(0) && !proc.HasExited)
        {
            ProcessModule[] arr = new ProcessModule[proc.Modules.Count];

            foreach (ProcessModule pm in proc.Modules)
            {
                if (pm.ModuleName == fileName)
                    return proc.ProcessName;
            }
        }
    }

    return null;
}
Run Code Online (Sandbox Code Playgroud)

  • 这只能告诉哪个进程保持_executable module_(dll)被锁定.它不会告诉您哪个进程已锁定,例如,您的xml文件. (14认同)

Ser*_*nte 15

您可以使用.NET FileStream类方法锁定和解锁,而不是使用互操作:

FileStream.Lock http://msdn.microsoft.com/en-us/library/system.io.filestream.lock.aspx

FileStream.Unlock http://msdn.microsoft.com/en-us/library/system.io.filestream.unlock.aspx

  • 实际上,这不是解决方案,因为如果文件被锁定,则无法创建FileStream实例.(将抛出异常) (30认同)

Sam*_*ron 7

您可以通过interop在您感兴趣的文件区域上调用LockFile.这不会抛出异常,如果成功,您将锁定文件的该部分(由您的进程保存),该锁将是一直持续到你调用UnlockFile或你的进程终止.


Tri*_*tan 7

DixonD的优秀答案的变体(上图).

public static bool TryOpen(string path,
                           FileMode fileMode,
                           FileAccess fileAccess,
                           FileShare fileShare,
                           TimeSpan timeout,
                           out Stream stream)
{
    var endTime = DateTime.Now + timeout;

    while (DateTime.Now < endTime)
    {
        if (TryOpen(path, fileMode, fileAccess, fileShare, out stream))
            return true;
    }

    stream = null;
    return false;
}

public static bool TryOpen(string path,
                           FileMode fileMode,
                           FileAccess fileAccess,
                           FileShare fileShare,
                           out Stream stream)
{
    try
    {
        stream = File.Open(path, fileMode, fileAccess, fileShare);
        return true;
    }
    catch (IOException e)
    {
        if (!FileIsLocked(e))
            throw;

        stream = null;
        return false;
    }
}

private const uint HRFileLocked = 0x80070020;
private const uint HRPortionOfFileLocked = 0x80070021;

private static bool FileIsLocked(IOException ioException)
{
    var errorCode = (uint)Marshal.GetHRForException(ioException);
    return errorCode == HRFileLocked || errorCode == HRPortionOfFileLocked;
}
Run Code Online (Sandbox Code Playgroud)

用法:

private void Sample(string filePath)
{
    Stream stream = null;

    try
    {
        var timeOut = TimeSpan.FromSeconds(1);

        if (!TryOpen(filePath,
                     FileMode.Open,
                     FileAccess.ReadWrite,
                     FileShare.ReadWrite,
                     timeOut,
                     out stream))
            return;

        // Use stream...
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • Boooyyyyy ......你最好把一些Thread.Sleep(200)放在那里,然后离开我的CPU吧! (7认同)
  • 尝试阅读@PaulKnopf 的评论,不要在脑海中使用愤怒的女友声音。 (2认同)

liv*_*ove 7

这是DixonD代码的变体,它增加了等待文件解锁的秒数,然后再试一次:

public bool IsFileLocked(string filePath, int secondsToWait)
{
    bool isLocked = true;
    int i = 0;

    while (isLocked &&  ((i < secondsToWait) || (secondsToWait == 0)))
    {
        try
        {
            using (File.Open(filePath, FileMode.Open)) { }
            return false;
        }
        catch (IOException e)
        {
            var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
            isLocked = errorCode == 32 || errorCode == 33;
            i++;

            if (secondsToWait !=0)
                new System.Threading.ManualResetEvent(false).WaitOne(1000);
        }
    }

    return isLocked;
}


if (!IsFileLocked(file, 10))
{
    ...
}
else
{
    throw new Exception(...);
}
Run Code Online (Sandbox Code Playgroud)


Sör*_*lau 6

然后在两行之间,另一个进程可以轻松锁定文件,为您提供与尝试避免开始时相同的问题:异常.

但是,通过这种方式,您会知道问题是暂时的,并且稍后重试.(例如,您可以编写一个线程,如果在尝试写入时遇到锁定,则会一直重试,直到锁定消失为止.)

另一方面,IOException本身并不具体,因为锁定是IO故障的原因.可能有一些原因不是暂时的.


Bri*_*ndy 6

您可以先尝试自行读取或锁定文件,以查看文件是否已锁定.

请在此处查看我的答案以获取更多信息.