基本上,我想在实际尝试打开文件之前检查我是否有权打开文件;除非必须,否则我不想使用 try/catch 进行此检查。是否有我可以事先检查的文件访问属性?
6 回答
过去我已经做过无数次了,几乎每次我都错了。
文件权限(甚至文件存在)是易变的——它们可以随时更改。由于墨菲定律,这尤其包括您检查文件和尝试打开文件之间的短暂时间。还有其他可能失败的原因,例如锁定或网络可用性和路径解析。如果您所在的区域您知道需要先检查,则更有可能出现错误的结果。然而奇怪的是,它永远不会发生在您的测试或开发环境中,它们往往是相当静态的。这使得以后很难追踪问题,并使这种错误很容易进入生产环境。
这意味着如果文件权限或存在错误,您仍必须准备好处理异常,尽管您进行了检查。异常处理代码是必需的,无论您是否提前检查文件,良好的异常处理程序可以提供存在或权限检查的所有功能。
但是异常处理不是很慢吗?我很高兴你问。是的,是的。事实上,展开堆栈以处理异常是您在单台计算机中可以做的最慢的事情。然而,重要的是要记住磁盘 I/O 甚至更慢——慢得多——调用.Exists()
函数或检查权限总是会强制在文件系统上执行额外的 I/O 操作。
因此,我们发现在尝试打开文件之前进行初始检查既多余又浪费。异常处理没有额外的好处。它实际上会伤害而不是帮助你的表现。它增加了必须维护的更多代码的成本。最后,它可以引入细微的错误。进行初步检查根本没有任何好处。
相反,这里正确的做法是立即尝试打开文件,而不进行初始检查,如果失败,则将您的精力投入到一个好的异常处理程序中。无论您是在检查权限、锁定,还是只是检查文件是否存在,都是如此。
总而言之:选择是每次使用更多代码为文件检查支付额外费用,或者只在某些时间使用更少的代码为异常处理支付较小但仍然很糟糕的费用。
给遇到类似问题的其他人的快速提示:
注意网络同步应用程序,例如 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;
}
首先,Joel Coehoorn 说了什么。
另外:您应该检查您希望避免使用 try/catch 的假设,除非您必须这样做。避免依赖于异常的逻辑(创建Exception
对象执行不佳)的典型原因可能与打开文件的代码无关。
我想,如果您正在编写一个List<FileStream>
通过打开目录子树中的每个文件来填充 a 的方法,并且您希望其中的大量文件无法访问,那么您可能需要在尝试打开文件之前检查文件权限,这样您就不会得到太多异常。但是你仍然会处理异常。此外,如果您正在编写执行此操作的方法,那么您的程序设计可能存在严重错误。
这是您正在寻找的解决方案
var fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read,
System.Security.AccessControl.AccessControlActions.View,
MyPath);
if (fileIOPermission.AllFiles == FileIOPermissionAccess.Read)
{
// Do your thing here...
}
这会根据所有文件的路径视图创建一个新的读取权限,然后检查它是否等于文件访问读取。
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;
}
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);
}
}
}