如何检查文件锁定?[复制]

How to check for file lock? [duplicate]

提问人:ricree 提问时间:8/4/2008 最后编辑:Hossein Narimani Radricree 更新时间:9/9/2021 访问量:237238

问:

有没有办法在不使用 try/catch 块的情况下检查文件是否被锁定?

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

C# .NET IO 文件锁

评论

12赞 JohnFx 2/10/2010
问题在于,除了锁定文件之外,还可能出于多种原因抛出 IOException。
5赞 Eric J. 12/24/2013
这是一个老问题,所有的旧答案都是不完整或错误的。我添加了一个完整且正确的答案。
1赞 amalgamate 2/3/2016
我知道这并不是问题的答案,但是一些正在寻求帮助的开发人员子集可能会有这个选项:如果您使用 System.Diagnostics.Process 启动拥有锁的进程,您可以 .WaitForExit() 中。

答:

133赞 Lasse V. Karlsen 8/4/2008 #1

不,不幸的是,如果你仔细想想,这些信息无论如何都毫无价值,因为文件可能会在下一秒被锁定(阅读:时间跨度短)。

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

如果您的代码如下所示:

if not locked then
    open and update file

然后,在这两行之间,另一个进程可以很容易地锁定文件,从而给您带来与开始时试图避免的相同问题:异常。

评论

16赞 DixonD 10/8/2010
如果文件被锁定,我们可以等待一段时间,然后重试。如果这是文件访问的另一种问题,那么我们应该传播异常。
14赞 Lasse V. Karlsen 10/8/2010
是的,但是独立检查文件是否被锁定是无用的,唯一正确的方法是尝试打开文件以达到您需要文件的目的,然后在此时处理锁定问题。然后,正如你所说,等待,或以另一种方式处理它。
2赞 ctusch 6/6/2013
你可以为访问权限争论同样的问题,尽管这当然不太可能。
8赞 Thiru 6/20/2013
@LasseV.卡尔森 进行先发制人检查的另一个好处是,您可以在尝试可能的长时间操作并在中途中断之前通知用户。当然,中途发生的锁定仍然是可能的,需要处理,但在许多情况下,这将大大有助于用户体验。
3赞 brianary 5/26/2018
在很多情况下,锁定测试不会“无用”。检查 IIS 日志(每天锁定一个文件以供写入)以查看哪个文件被锁定,这是此类日志记录情况的典型示例。可以很好地识别系统上下文,以便从锁定测试中获取价值。“✗ 如果可能的话,不要对正常的控制流使用异常。”learn.microsoft.com/en-us/dotnet/standard/design-guidelines/...
4赞 Sören Kuklau 8/18/2008 #2

然后,在这两行之间,另一个进程可以很容易地锁定文件,从而给您带来与开始时试图避免的相同问题:异常。

但是,这样,您就会知道问题是暂时的,稍后重试。(例如,您可以编写一个线程,如果在尝试写入时遇到锁,则会不断重试,直到锁消失。

另一方面,IOException 本身不够具体,以至于锁定是 IO 故障的原因。可能有一些不是暂时的原因。

4赞 Brian R. Bondy 3/9/2009 #3

您可以通过先尝试自己读取或锁定文件来查看文件是否已锁定。

有关更多信息,请参阅我的答案

5赞 Sam Saffron 7/24/2009 #4

可以通过互操作在您感兴趣的文件区域上调用 LockFile。这不会引发异常,如果成功,您将在文件的该部分(由您的进程保存)上有一个锁,该锁将一直保持,直到您调用 UnlockFile 或您的进程死亡。

14赞 Sergio Vicente 3/9/2010 #5

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

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

评论

1赞 BrainSlugs83 10/14/2011
这确实是正确的答案,因为它使用户不仅能够锁定/解锁文件,还能够锁定/解锁文件的某些部分。所有“没有交易就无法做到这一点”的评论都可能引起合理的担忧,但毫无用处,因为它们假装该功能不存在,或者在功能不存在时以某种方式隐藏。
35赞 Zé Carlos 1/28/2012
实际上,这不是一个解决方案,因为如果文件被锁定,则无法创建 FileStream 的实例。(将抛出异常)
1赞 Alexander Høst 3/5/2022
我认为这是一个解决方案。如果您的目标只是检查文件锁定。抛出的异常可以为您提供所需的答案。
187赞 DixonD 7/8/2010 #6

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

public class FileManager
{
    private string _fileName;

    private int _numberOfTries;

    private int _timeIntervalBetweenTries;

    private FileStream GetStream(FileAccess fileAccess)
    {
        var tries = 0;
        while (true)
        {
            try
            {
                return File.Open(_fileName, FileMode.Open, fileAccess, Fileshare.None); 
            }
            catch (IOException e)
            {
                if (!IsFileLocked(e))
                    throw;
                if (++tries > _numberOfTries)
                    throw new MyCustomException("The file is locked too long: " + e.Message, e);
                Thread.Sleep(_timeIntervalBetweenTries);
            }
        }
    }

    private static bool IsFileLocked(IOException exception)
    {
        int errorCode = Marshal.GetHRForException(exception) & ((1 << 16) - 1);
        return errorCode == 32 || errorCode == 33;
    }

    // other code

}

评论

2赞 Eric J. 1/23/2014
@kite:现在有更好的方法 stackoverflow.com/a/20623302/141172
5赞 jocull 4/2/2014
如果在您再次尝试打开文件之间有其他东西抢走了它怎么办?比赛条件啊!return false
2赞 DixonD 6/9/2016
@RenniePet 以下页面应该更有帮助:msdn.microsoft.com/en-us/library/windows/desktop/...相关错误ERROR_SHARING_VIOLATION和ERROR_LOCK_VIOLATION
2赞 BartoszKP 4/19/2017
如果将结果与常量进行比较,那么在这里进行位掩码的目的是什么?另外,有副作用,从.NET 4.5开始可以直接读取。GetHRForExceptionHResult
2赞 taiji123 7/15/2019
@BartoszKP 没错,谢谢。以下是“catch”子句的更新内容:const int ERROR_SHARING_VIOLATION = 0x20; const int ERROR_LOCK_VIOLATION = 0x21; int errorCode = e.HResult & 0x0000FFFF; return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION;
16赞 Aralmo 4/1/2011 #7

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

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

评论

16赞 Constantin 7/9/2012
这只能判断哪个进程使可执行模块 (dll) 保持锁定状态。它不会告诉您哪个进程锁定了您的 xml 文件。
9赞 Tristan 1/3/2013 #8

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

用法:

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

评论

0赞 Contango 2/6/2013
这是迄今为止唯一可行的解决方案。它起作用了。
7赞 Paul Knopf 4/16/2014
呜......你最好把一些 Thread.Sleep(200) 放进去,然后离开我的 CPU!
1赞 Tristan 4/18/2014
你想睡哪个部位?为什么?
1赞 DixonD 8/22/2014
@Tristan我猜,Paul Knopf 的意思是在访问尝试之间使用 Thread.Sleep。
2赞 Phil Cooper 10/30/2015
试着阅读@PaulKnopf的评论,不要在脑海中使用愤怒的女朋友的声音。
7赞 live-love 9/25/2013 #9

下面是 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(...);
}

评论

1赞 DixonD 1/10/2014
好吧,我在最初的答案中做了同样的事情,直到有人决定简化它:) stackoverflow.com/posts/3202085/revisions
-1赞 Bart Calixto 12/17/2013 #10

我最终做的是:

internal void LoadExternalData() {
    FileStream file;

    if (TryOpenRead("filepath/filename", 5, out file)) {
        using (file)
        using (StreamReader reader = new StreamReader(file)) {
         // do something 
        }
    }
}


internal bool TryOpenRead(string path, int timeout, out FileStream file) {
    bool isLocked = true;
    bool condition = true;

    do {
        try {
            file = File.OpenRead(path);
            return true;
        }
        catch (IOException e) {
            var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
            isLocked = errorCode == 32 || errorCode == 33;
            condition = (isLocked && timeout > 0);

            if (condition) {
                // we only wait if the file is locked. If the exception is of any other type, there's no point on keep trying. just return false and null;
                timeout--;
                new System.Threading.ManualResetEvent(false).WaitOne(1000);
            }
        }
    }
    while (condition);

    file = null;
    return false;
}

评论

1赞 VoteCoffee 4/29/2014
您应该考虑对文件使用 Using 块
1赞 Endrju 8/12/2014
使用代替System.Threading.Thread.Sleep(1000)new System.Threading.ManualResetEvent(false).WaitOne(1000)
164赞 Eric J. 12/17/2013 #11

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

很久以前,不可能可靠地获取锁定文件的进程列表,因为 Windows 根本没有跟踪该信息。为了支持重启管理器 API,现在将跟踪该信息。从 Windows Vista 和 Windows Server 2008 开始提供重启管理器 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;
    }
}

更新

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

评论

17赞 Serj Sagan 12/23/2013
这里唯一真正回答 OP 问题的答案......好!
6赞 Coder14 12/9/2014
如果文件位于网络共享上,并且文件可能被锁定在另一台电脑上,这是否有效?
8赞 Jonathan D 2/19/2015
我刚刚使用了这个,它确实可以在整个网络上运行。
5赞 Melvyn 5/12/2016
如果有人感兴趣,我创建了一个受此答案启发的要点,但更简单,并通过 msdn 的正确格式文档进行了改进。我还从Raymond Chen的文章中汲取了灵感,并处理了比赛条件。顺便说一句,我注意到此方法大约需要 30 毫秒才能运行(仅 RmGetList 方法就需要 20 毫秒),而 DixonD 的方法尝试获取锁需要不到 5 毫秒......如果您打算在紧密循环中使用它,请记住这一点......
5赞 Melvyn 8/16/2017
@VadimLevkovsky哦,对不起,这里有一个工作链接:gist.github.com/mlaily/9423f1855bb176d52a327f5874915a97
0赞 Thom Schumacher 12/23/2015 #12

同样的事情,但在 Powershell 中

function Test-FileOpen
{
    Param
    ([string]$FileToOpen)
    try
    {
        $openFile =([system.io.file]::Open($FileToOpen,[system.io.filemode]::Open))
        $open =$true
        $openFile.close()
    }
    catch
    {
        $open = $false
    }
    $open
}