CSharp - 是否有一种方法来检查文件正在使用?

  显示原文与译文双语对照的内容

我正在用 C# 编写一个程序,它需要重复访问 1图像文件。 大部分时间,但如果我运行的计算机快,它将尝试访问文件之前被保存回 文件系统 和抛出一个错误: "另一个进程正在使用的文件" 。

我想找到一个解决这个问题的办法,但我所有的google只有创建检查通过使用异常处理。 这是我的宗教,所以我想知道是否有人有更好的方法来做它?

时间:

我在过去的几年里使用过这个代码,但它没有任何问题。

理解你对使用异常的犹豫,但你不能一直回避它们:


protected virtual bool IsFileLocked(FileInfo file)
{
 FileStream stream = null;

 try
 {
 stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
 }
 catch (IOException)
 {
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
 return true;
 }
 finally
 {
 if (stream!= null)
 stream.Close();
 }

//file is not locked
 return false;
}

你可能会遇到一个线程争用条件,其中有一个被用来作为安全漏洞的文档示例。 如果你检查该文件是否可用,然后尝试使用它,你就可以在该点抛出,恶意用户可以在你的代码中强制和利用它。

你最好的选择是尝试获取文件句柄。


try
{
 using (Stream stream = new FileStream("MyFilename.txt", FileMode.Open))
 {
//File/Stream manipulating code here
 }
} catch {
//check here why it failed and ask user to retry if the file is in use.
}

使用这里选项检查文件是否已经锁定:


using System.IO;
using System.Runtime.InteropServices;
internal static class Helper
{
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;

private static bool IsFileLocked(Exception exception)
{
 int errorCode = Marshal.GetHRForException(exception) & ((1 <<16) - 1);
 return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION;
}

internal static bool CanReadFile(string filePath)
{
//Try-Catch so we dont crash the program and can check the exception
 try {
//The"using" is important because FileStream implements IDisposable and
//"using" will avoid a heap exhaustion situation when too many handles 
//are left undisposed.
 using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) {
 if (fileStream!= null) fileStream.Close();//This line is me being overly cautious, fileStream will never be null unless an exception occurs... and I know the"using" does it but its helpful to be explicit - especially when we encounter errors - at least for me anyway!
 }
 }
 catch (IOException ex) {
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
 if (IsFileLocked(ex)) {
//do something, eg File.Copy or present the user with a MsgBox - I do not recommend Killing the process that is locking the file
 return false;
 }
 }
 finally
 { }
 return true;
}
}

出于性能原因,我建议你阅读同一操作中的文件内容。 以下是一些示例:


public static byte[] ReadFileBytes(string filePath)
{
 byte[] buffer = null;
 try
 {
 using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
 {
 int length = (int)fileStream.Length;//get file length
 buffer = new byte[length];//create buffer
 int count;//actual number of bytes read
 int sum = 0;//total number of bytes read

//read until Read method returns 0 (end of the stream has been reached)
 while ((count = fileStream.Read(buffer, sum, length - sum))> 0)
 sum += count;//sum is a buffer offset for next reading

 fileStream.Close();//This is not needed, just me being paranoid and explicitly releasing resources ASAP
 }
 }
 catch (IOException ex)
 {
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
 if (IsFileLocked(ex))
 {
//do something? 
 }
 }
 catch (Exception ex)
 {
 }
 finally
 {
 }
 return buffer;
}

public static string ReadFileTextWithEncoding(string filePath)
{
 string fileContents = string.Empty;
 byte[] buffer;
 try
 {
 using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
 {
 int length = (int)fileStream.Length;//get file length
 buffer = new byte[length];//create buffer
 int count;//actual number of bytes read
 int sum = 0;//total number of bytes read

//read until Read method returns 0 (end of the stream has been reached)
 while ((count = fileStream.Read(buffer, sum, length - sum))> 0)
 {
 sum += count;//sum is a buffer offset for next reading
 }

 fileStream.Close();//Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

//Depending on the encoding you wish to use - I'll leave that up to you
 fileContents = System.Text.Encoding.Default.GetString(buffer);
 }
 }
 catch (IOException ex)
 {
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
 if (IsFileLocked(ex))
 {
//do something? 
 }
 }
 catch (Exception ex)
 {
 }
 finally
 { } 
 return fileContents;
}

public static string ReadFileTextNoEncoding(string filePath)
{
 string fileContents = string.Empty;
 byte[] buffer;
 try
 {
 using (FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
 {
 int length = (int)fileStream.Length;//get file length
 buffer = new byte[length];//create buffer
 int count;//actual number of bytes read
 int sum = 0;//total number of bytes read

//read until Read method returns 0 (end of the stream has been reached)
 while ((count = fileStream.Read(buffer, sum, length - sum))> 0) 
 {
 sum += count;//sum is a buffer offset for next reading
 }

 fileStream.Close();//Again - this is not needed, just me being paranoid and explicitly releasing resources ASAP

 char[] chars = new char[buffer.Length/sizeof(char) + 1];
 System.Buffer.BlockCopy(buffer, 0, chars, 0, buffer.Length);
 fileContents = new string(chars);
 }
 }
 catch (IOException ex)
 {
//THE FUNKY MAGIC - TO SEE IF THIS FILE REALLY IS LOCKED!!!
 if (IsFileLocked(ex))
 {
//do something? 
 }
 }
 catch (Exception ex)
 {
 }
 finally
 {
 }

 return fileContents;
}

自己试试:


byte[] output1 = Helper.ReadFileBytes(@"c:temptest.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:temptest.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:temptest.txt");

下面是接受的答案的powershell版本。


function IsFileLocked($filename) {

 $result = $false

 $fileinfo = [System.IO.FileInfo] (gi $filename).fullname

 try {
 $stream = $fileInfo.Open([System.IO.FileMode]"Open",[System.IO.FileAccess]"ReadWrite",[System.IO.FileShare]"None")
 $stream.Dispose()
 } catch [System.IO.IOException] {
 $result = $true
 }

 $result
}

可能你可以使用 FileSystemWatcher 并监视已经更改的事件。

我自己没有使用过这个,但可能值得一试。 如果filesystemwatcher对于这种情况来说有点笨重,我将使用 try/catch/sleep 循环。

我知道的唯一方法是使用不太快的Win32独占锁定 API,但存在示例。

大多数人,对于这个简单的解决方案,仅仅是 try/catch/sleep 循环。


static bool FileInUse(string path)
 {
 try
 {
 using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate))
 {
 fs.CanWrite
 }
 return false;
 }
 catch (IOException ex)
 {
 return true;
 }
 }

string filePath ="C:Documents And Settingsyourfilename";
bool isFileInUse;

isFileInUse = FileInUse(filePath);

//Then you can do some checking
if (isFileInUse)
 Console.WriteLine("File is in use");
else
 Console.WriteLine("File is not in use");

希望有帮助!

根据我的经验,你通常想做这个,然后'保护'文件做一些花哨的,然后使用'受保护'文件。 如果你只想使用一个文件,你可以用 Jeremy 。汤普森回答的技巧。 但是,如果你试图在许多文件( 比如说,当你写一个安装程序时) 上执行这里操作,那么你就会受到相当多的伤害。

一个非常优雅的方式可以帮助解决这个问题是通过使用你的文件系统将不允许你更改文件夹名称,如果一个文件被使用。 将文件夹放在同一个文件系统中,它就会像一个魅力一样工作。

请注意,你应该知道可以使用的明显方法。 毕竟,文件将不会被锁定。 另外,请注意,还有其他导致 Move 操作失败的原因。 显然正确的错误处理( MSDN ) 可以帮助你。


var originalFolder = @"c:myHugeCollectionOfFiles";//your folder name here
var someFolder = Path.Combine(originalFolder,"..", Guid.NewGuid().ToString("N"));

try
{
 Directory.Move(originalFolder, someFolder);

//Use files
}
catch//TODO: proper exception handling
{
//Inform user, take action
}
finally
{
 Directory.Move(someFolder, originalFolder);
}

对于单独的文件,我将坚持 Jeremy Thompson发布的锁定建议。

尝试移动/复制文件到临时目录。 如果可以,它没有锁,你可以在临时目录中安全工作而不获取锁。 或者尝试在x 秒内再次移动它。

我使用这个变通方法,但在检查使用IsFileLocked函数的文件锁定和打开文件之间有一个时间段。 在这个timespan中,其他一些线程可以打开文件,所以我将得到 IOException 。

所以,我为此添加了额外的代码。 在我的情况下,我需要加载 XDocument:


 XDocument xDoc = null;

 while (xDoc == null)
 {
 while (IsFileBeingUsed(_interactionXMLPath))
 {
 Logger.WriteMessage(Logger.LogPrioritet.Warning,"Deserialize can not open XML file. is being used by another process. wait...");
 Thread.Sleep(100);
 }
 try
 {
 xDoc = XDocument.Load(_interactionXMLPath);
 }
 catch
 {
 Logger.WriteMessage(Logger.LogPrioritet.Error,"Load working!!!!!");
 }
 }

你觉得怎么样我能改变一些东西? 也许我根本不用使用IsFileBeingUsed函数?

谢谢

...