如何轻松检查.NET中文件的访问是否被拒绝?


100

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


2
更改标签时的标题:“即时更正”。不是开玩笑。
Joel Coehoorn

6
同意-我希望有一个TryOpen(即Try-Parse模式)。
特里斯坦,

Answers:


157

过去,我无数次这样做,而且几乎每次都这样做,甚至尝试都错了。

文件权限(甚至文件存在)是易变的 -它们可以随时更改。多亏墨菲定律,这尤其包括了从检查文件到尝试打开文件之间的短暂时间。如果您位于需要首先检查的区域,则更可能进行更改。但是奇怪的是,它在您的测试或开发环境中永远不会发生,而这些环境通常是相当静态的。这使得问题很难在以后找到,并使此类错误更易于投入生产。

这意味着,尽管进行了检查,但是如果文件许可权或存在性很差,您仍然必须能够处理该异常。无论您是否事先检查文件的权限,都需要异常处理代码。异常处理代码提供了存在性或权限检查的所有功能。此外,虽然已知这样的异常处理程序很慢,但重要的是要记住,磁盘的I / O甚至更慢... 慢得多...并且调用.Exists()函数或检查权限将强制执行额外的行程文件系统。

总之,在尝试打开文件之前进行初始检查既多余又浪费。异常处理没有额外的好处,它实际上会损害而不是帮助您的性能,它会增加必须维护的代码的成本,并且会在代码中引入一些细微的错误。进行初始检查根本没有任何余地。相反,这里的正确做法是尝试打开文件,如果失败,则将您的精力放在一个好的异常处理程序中。即使您只是检查文件是否存在,也是如此。此推理适用于任何易失性资源。


5
究竟。这是比赛条件的经典示例。
Powerlord

3
korro:无论如何,您都必须能够处理错误的错误权限,这会使初始检查变得多余和浪费。
Joel Coehoorn

2
初步检查可以帮助妥善处理常见的特定错误-向前看通常比将特定异常属性与特定原因匹配更容易。尝试/捕获仍然是必需的。
peterchen'3

5
在尝试打开文件之前,此答案在某些情况下没有回答“如何检查我是否有权打开文件”的问题。这种情况很可能是这样的:如果在该情况下不允许许可,则即使在检查许可后就可以很好地授予许可,该软件也不会尝试读取文件。
Triynko

5
当您只在乎权限瞬间时,权限是否可变并不重要。应该始终处理失败,但是,如果您检查了读取许可权并且不存在该许可权,那么即使有可能在一秒钟之后您可以访问该文件,您也可能希望跳过读取文件。您必须在某处画线。
Triynko

25

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

注意Web同步应用程序,例如DropBox。我只花了2个小时就认为.NET中的“使用”语句(处置模式)已损坏。

我最终意识到,Dropbox在后台不断读取和写入文件,以便同步它们。

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

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

因此,至少现在我知道我需要一个更强大的File Open函数(即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;
}

3
@Ash我认为您没有正确阅读问题,他想避免尝试抓住。
拉维沙

10
@Ravisha,您是否还阅读了Joel的最高投票答案?正如Joel所说,“您要做的只是尝试打开文件并在失败时处理异常”。请不要仅仅因为您不喜欢无法避免的事实而投票。
Ash Ash,2010年

感谢您的代码!一件事,最好使用例如使用Tazeem答案在这里
Cel

给定您返回的文件流,则using调用者必须使用该文件流……
Cel

@Cel- using在这里无法使用。在using块的末尾,fs将被强制关闭。您将为调用者提供一个已关闭(因此无用)的文件流!
ToolmakerSteve

4

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

var fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read,
                                            System.Security.AccessControl.AccessControlActions.View,
                                            MyPath);

if (fileIOPermission.AllFiles == FileIOPermissionAccess.Read)
{
    // Do your thing here...
}

这将基于视图为所有文件的路径创建新的读取许可,然后检查其是否等于文件访问读取。


3

首先,乔尔·库洪(Joel Coehoorn)说的话。

另外:除非有必要,否则您应该检查一下避免使用try / catch的潜在假设。避免依赖异常的逻辑(创建Exception对象的性能较差)的典型原因可能与打开文件的代码无关。

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


-1
public static bool IsFileLocked(string filename)
        {
            bool Locked = false;
            try
            {
                FileStream fs =
                    File.Open(filename, FileMode.OpenOrCreate,
                    FileAccess.ReadWrite, FileShare.None);
                fs.Close();
            }
            catch (IOException ex)
            {
                Locked = true;
            }
            return Locked;
        }

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

8
-1:使用“投掷”;而不是“ throwealtyAccessException;”。您正在丢失堆栈跟踪。
约翰·桑德斯

为什么attempts通过ref?这是没有意义的。无论是做测试的<=,而不是只==
康拉德·鲁道夫

1
@John:好吧,在这种情况下是可取的丢失递归调用的(深度嵌套)堆栈跟踪,所以我认为在这种情况下throw ex实际上是正确的做法。
康拉德·鲁道夫

2
@Konrad:@Rudzitis:我正在更改-1的理由。这比通过“ throw ex”破坏堆栈更糟糕。您实际上是在堆叠深度很重要的时候,通过递归通过人为地引入额外的堆叠级别来拧紧堆叠。这是一个迭代问题,而不是递归问题。
约翰·桑德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.