【问题标题】:how can you easily check if access is denied for a file in .NET?如何轻松检查 .NET 中文件的访问是否被拒绝?
【发布时间】:2010-09-20 22:12:48
【问题描述】:

基本上,我想在实际尝试打开文件之前检查我是否有权打开文件;除非必须,否则我不想使用 try/catch 进行此检查。是否有我可以事先检查的文件访问属性?

【问题讨论】:

  • 我更改标签时的标题:“我正在纠正”。不开玩笑。
  • 同意——我希望有一个 TryOpen(即 Try-Parse 模式)。

标签: c# .net file-access


【解决方案1】:

过去我已经做过无数次了,几乎每次我都做错了尝试。

文件权限(甚至文件存在)是易变的——它们可以随时更改。感谢墨菲定律,这尤其是包含了从您检查文件到您尝试打开文件之间的短暂时间。还有其他可能失败的原因,例如锁定或网络可用性和路径解析。如果您所在的区域您知道需要先检查,则更有可能出现错误的结果。然而奇怪的是,它永远不会发生在您的测试或开发环境中,它们往往是相当静态的。这使得问题难以在以后追查,并使得这种错误很容易进入生产环境。

这意味着,如果文件权限或存在错误,您仍必须准备好处理异常,尽管您进行了检查。异常处理代码是必需的,不管你是否提前检查文件,好的异常处理程序可以提供所有存在或权限检查的功能。

但是异常处理不是很慢吗?我很高兴你问。是的,是的。事实上,展开堆栈以处理异常是您在单台计算机中可以做的最慢的事情。但是,重要的是要记住,磁盘 I/O 甚至更慢 - 很多 慢 - 并且调用 .Exists() 函数或检查权限将始终强制在文件系统上执行额外的 I/O 操作。

因此,我们发现在尝试打开文件之前进行初始检查既多余又浪费。异常处理没有额外的好处。它实际上会伤害而不是帮助你的表现。它增加了必须维护的更多代码的成本。最后,它可以引入细微的错误。进行初步检查根本没有任何好处。

相反,这里正确的做法是立即尝试打开文件,而不进行初始检查,如果失败,则将您的精力投入到一个好的异常处理程序中。无论您是检查权限、锁定,还是只是检查文件是否存在,都是如此。

总而言之:选择是每次使用更多代码为文件检查支付额外费用,或者只在某些时间使用更少的代码为异常处理支付较小但仍然很糟糕的费用。

【讨论】:

  • 没错。这是竞争条件的经典示例。
  • korro:无论如何,您必须能够处理错误权限,这使得初始检查变得多余和浪费。
  • 初始检查有助于优雅地处理常见的特定错误 - 向前看通常比将特定异常属性与特定原因匹配更容易。 try/catch 仍然是强制性的。
  • 在尝试打开文件之前,此答案在某些情况下没有回答“如何检查我是否有权打开文件”的问题。这种情况很可能是,如果在该实例中不允许权限,软件将不会尝试读取文件,即使权限很可能在检查权限后被授予。
  • 权限是否不稳定并不重要,因为您只关心它们在那一刻是什么。应该始终处理失败,但是如果您检查读取权限并且它不存在,那么您可能希望跳过读取文件,即使一秒钟后您可能具有访问权限。你必须在某处画线。
【解决方案2】:

首先,Joel Coehoorn 说了什么。

另外:您应该检查您希望避免使用 try/catch 的假设,除非您必须这样做。避免依赖于异常的逻辑(创建Exception 对象性能不佳)的典型原因可能与打开文件的代码无关。

我想如果您正在编写一个通过打开目录子树中的每个文件来填充List<FileStream> 的方法,并且您希望其中的大量文件无法访问,那么您可能需要在尝试打开文件之前检查文件权限这样您就不会遇到太多异常。但是你仍然会处理异常。此外,如果您正在编写执行此操作的方法,那么您的程序设计可能存在严重错误。

【讨论】:

    【解决方案3】:

    给遇到类似问题的其他人的快速提示:

    注意网络同步应用程序,例如 DropBox。我刚刚花了 2 个小时思考“使用”语句(Dispose 模式)在 .NET 中被破坏了。

    我最终意识到 Dropbox 会在后台持续读写文件,以便同步它们。

    猜猜我的 Visual Studio Projects 文件夹在哪里?当然是在“我的 Dropbox”文件夹中。

    因此,当我在调试模式下运行我的应用程序时,它正在读取和写入的文件也不断被 DropBox 访问以与 DropBox 服务器同步。这导致了锁定/访问冲突。

    所以至少我现在知道我需要一个更强大的文件打开函数(即 TryOpen(),它将进行多次尝试)。我很惊讶它还不是框架的内置部分。

    [更新]

    这是我的辅助函数:

    /// <summary>
    /// Tries to open a file, with a user defined number of attempt and Sleep delay between attempts.
    /// </summary>
    /// <param name="filePath">The full file path to be opened</param>
    /// <param name="fileMode">Required file mode enum value(see MSDN documentation)</param>
    /// <param name="fileAccess">Required file access enum value(see MSDN documentation)</param>
    /// <param name="fileShare">Required file share enum value(see MSDN documentation)</param>
    /// <param name="maximumAttempts">The total number of attempts to make (multiply by attemptWaitMS for the maximum time the function with Try opening the file)</param>
    /// <param name="attemptWaitMS">The delay in Milliseconds between each attempt.</param>
    /// <returns>A valid FileStream object for the opened file, or null if the File could not be opened after the required attempts</returns>
    public FileStream TryOpen(string filePath, FileMode fileMode, FileAccess fileAccess,FileShare fileShare,int maximumAttempts,int attemptWaitMS)
    {
        FileStream fs = null;
        int attempts = 0;
    
        // Loop allow multiple attempts
        while (true)
        {
            try
            {
                fs = File.Open(filePath, fileMode, fileAccess, fileShare);
    
                //If we get here, the File.Open succeeded, so break out of the loop and return the FileStream
                break;
            }
            catch (IOException ioEx)
            {
                // IOExcception is thrown if the file is in use by another process.
    
                // Check the numbere of attempts to ensure no infinite loop
                attempts++;
                if (attempts > maximumAttempts)
                {
                    // Too many attempts,cannot Open File, break and return null 
                    fs = null;
                    break;
                }
                else
                {
                    // Sleep before making another attempt
                    Thread.Sleep(attemptWaitMS);
    
                }
    
            }
    
        }
        // Reutn the filestream, may be valid or null
        return fs;
    }
    

    【讨论】:

    • @Ash 我认为你没有正确阅读问题他想避免尝试捕捉。
    • @Ravisha,您是否阅读过 Joel 的最高投票答案?正如 Joel 所说,“您所做的只是尝试打开文件并在失败时处理异常”。请不要仅仅因为您不喜欢无法避免的事实而投反对票。
    • 感谢代码!一件事,使用例如使用可能会更好。见Tazeem answer here
    • 如果您返回文件流,那么调用者必须使用using ...
    • @Cel - using 在这里不起作用。在 using 块结束时,fs 将被强制关闭。您将给调用者一个 CLOSED(如此无用)的文件流!
    【解决方案4】:
    public static FileStream GetFileStream(String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, ref int attempts, int attemptWaitInMilliseconds)
    {            
        try
        {
             return File.Open(filePath, fileMode, fileAccess, fileShare);
        }
        catch (UnauthorizedAccessException unauthorizedAccessException)
        {
            if (attempts <= 0)
            {
                throw unauthorizedAccessException;
            }
            else
            {
                Thread.Sleep(attemptWaitInMilliseconds);
                attempts--;
                return GetFileStream(filePath, fileMode, fileAccess, fileShare, ref attempts, attemptWaitInMilliseconds);
            }
        }
    }
    

    【讨论】:

    • -1:使用“投掷;”不是“抛出未授权访问异常;”。您正在丢失堆栈跟踪。
    • 为什么attempts会被ref传递?这是没有意义的。也没有测试&lt;= 而不仅仅是==
    • @John:好吧,在这种情况下,希望丢失递归调用的(深度嵌套的)堆栈跟踪,所以我认为在这种情况下throw ex 实际上是 正确的事情去做。
    • @Konrad:@Rudzitis:我正在改变我选择 -1 的原因。这比通过“throw ex”搞砸堆栈更糟糕。在堆栈深度实际上很重要的时候,您通过递归人为地诱导额外的堆栈级别来搞砸堆栈。这是一个迭代问题,而不是递归问题。
    【解决方案5】:

    这是您正在寻找的解决方案

    var fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read,
                                                System.Security.AccessControl.AccessControlActions.View,
                                                MyPath);
    
    if (fileIOPermission.AllFiles == FileIOPermissionAccess.Read)
    {
        // Do your thing here...
    }
    

    这会根据所有文件的路径视图创建一个新的读取权限,然后检查它是否等于文件访问读取。

    【讨论】:

    • 不适用于我拥有权限的任何文件
    【解决方案6】:
    public static bool IsFileLocked(string filename)
    {
        try
        {
            using var fs = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
        }
        catch (IOException)
        {
            return true;
        }
        return false;
    }
    

    【讨论】:

    • 对于任何来这里寻求快速复制粘贴解决方案的人来说,这可能就是它。其他答案中的 FileIOPermission 解决方案在 .Net Core 中已弃用。不过,请阅读有关比赛条件的答案。对于我当前的用例,这非常有效。我只需要等待/检测新文件,然后检测何时释放写锁,这样我就可以开始处理它了。没有竞争条件问题。
    • 这在功能上与this answer from 2009 相同,后者也使用 IOException。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多