Answers:
过去,我无数次这样做,而且几乎每次都这样做,甚至尝试都错了。
文件权限(甚至文件存在)是易变的 -它们可以随时更改。多亏墨菲定律,这尤其包括了从检查文件到尝试打开文件之间的短暂时间。如果您位于需要首先检查的区域,则更可能进行更改。但是奇怪的是,它在您的测试或开发环境中永远不会发生,而这些环境通常是相当静态的。这使得问题很难在以后找到,并使此类错误更易于投入生产。
这意味着,尽管进行了检查,但是如果文件许可权或存在性很差,您仍然必须能够处理该异常。无论您是否事先检查文件的权限,都需要异常处理代码。异常处理代码提供了存在性或权限检查的所有功能。此外,虽然已知这样的异常处理程序很慢,但重要的是要记住,磁盘的I / O甚至更慢... 要慢得多...并且调用.Exists()函数或检查权限将强制执行额外的行程文件系统。
总之,在尝试打开文件之前进行初始检查既多余又浪费。异常处理没有额外的好处,它实际上会损害而不是帮助您的性能,它会增加必须维护的代码的成本,并且会在代码中引入一些细微的错误。进行初始检查根本没有任何余地。相反,这里的正确做法是尝试打开文件,如果失败,则将您的精力放在一个好的异常处理程序中。即使您只是检查文件是否存在,也是如此。此推理适用于任何易失性资源。
给其他遇到类似问题的人的快速提示:
注意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;
}
using
调用者必须使用该文件流……
using
在这里无法使用。在using块的末尾,fs
将被强制关闭。您将为调用者提供一个已关闭(因此无用)的文件流!
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;
}
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);
}
}
}
attempts
通过ref?这是没有意义的。无论是做测试的<=
,而不是只==
。
throw ex
实际上是正确的做法。