C# 有没有办法检查文件是否正在使用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/876473/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a way to check if a file is in use?
提问by Dawsy
I'm writing a program in C# that needs to repeatedly access 1 image file. Most of the time it works, but if my computer's running fast, it will try to access the file before it's been saved back to the filesystem and throw an error: "File in use by another process".
我正在用 C# 编写一个需要重复访问 1 个图像文件的程序。大多数情况下它都可以工作,但是如果我的计算机运行速度很快,它会在将文件保存回文件系统之前尝试访问该文件并抛出错误:“另一个进程正在使用文件”。
I would like to find a way around this, but all my Googling has only yielded creating checks by using exception handling. This is against my religion, so I was wondering if anyone has a better way of doing it?
我想找到一种方法来解决这个问题,但我所有的谷歌搜索都只通过使用异常处理产生了创建检查。这违反了我的宗教信仰,所以我想知道是否有人有更好的方法来做到这一点?
采纳答案by ChrisW
Updated NOTE on this solution: Checking with FileAccess.ReadWrite
will fail for Read-Only files so the solution has been modified to check with FileAccess.Read
. While this solution works because trying to check with FileAccess.Read
will fail if the file has a Write or Read lock on it, however, this solution will not work if the file doesn't have a Write or Read lock on it, i.e. it has been opened (for reading or writing) with FileShare.Read or FileShare.Write access.
关于此解决方案的更新说明:检查FileAccess.ReadWrite
只读文件将失败,因此该解决方案已修改为检查FileAccess.Read
。虽然此解决方案有效,因为FileAccess.Read
如果文件上有写入或读取锁定,则尝试检查将失败,但是,如果文件上没有写入或读取锁定,即已打开,则此解决方案将不起作用(用于读取或写入)具有 FileShare.Read 或 FileShare.Write 访问权限。
ORIGINAL:I've used this code for the past several years, and I haven't had any issues with it.
原文:过去几年我一直在使用这个代码,我没有遇到任何问题。
Understand your hesitation about using exceptions, but you can't avoid them all of the time:
理解你对使用异常的犹豫,但你不能一直避免它们:
protected virtual bool IsFileLocked(FileInfo file)
{
try
{
using(FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
{
stream.Close();
}
}
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;
}
//file is not locked
return false;
}
回答by Luke Schafer
the only way I know of is to use the Win32 exclusive lock API which isn't too speedy, but examples exist.
我所知道的唯一方法是使用 Win32 独占锁 API,它不是太快,但存在示例。
Most people, for a simple solution to this, simply to try/catch/sleep loops.
大多数人,对于这个简单的解决方案,只是尝试/捕捉/睡眠循环。
回答by Karl Johan
Perhaps you could use a FileSystemWatcherand watch for the Changed event.
也许您可以使用FileSystemWatcher并观察 Changed 事件。
I haven't used this myself, but it might be worth a shot. If the filesystemwatcher turns out to be a bit heavy for this case, I would go for the try/catch/sleep loop.
我自己没有使用过这个,但它可能值得一试。如果文件系统观察器在这种情况下变得有点重,我会选择 try/catch/sleep 循环。
回答by Spence
You can suffer from a thread race condition on this which there are documented examples of this being used as a security vulnerability. If you check that the file is available, but then try and use it you could throw at that point, which a malicious user could use to force and exploit in your code.
您可能会受到线程竞争条件的影响,有记录的示例说明将其用作安全漏洞。如果您检查该文件是否可用,然后尝试使用它,您可能会在此时抛出该文件,恶意用户可能会使用它来强制并利用您的代码。
Your best bet is a try catch / finally which tries to get the file handle.
您最好的选择是尝试捕获/最终尝试获取文件句柄。
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.
}
回答by Carra
Try and move/copy the file to a temp dir. If you can, it has no lock and you can safely work in the temp dir without getting locks. Else just try to move it again in x seconds.
尝试将文件移动/复制到临时目录。如果可以,它没有锁定,您可以安全地在临时目录中工作而不会锁定。否则只需尝试在 x 秒内再次移动它。
回答by Julian
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 Settings\yourfilename";
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");
Hope this helps!
希望这可以帮助!
回答by zzfima
I use this workaround, but i have a timespan between when i check the file locking with IsFileLocked function and when i open the file. In this timespan some other thread can open the file, so i will get IOException.
我使用此解决方法,但在使用 IsFileLocked 函数检查文件锁定和打开文件之间有一个时间跨度。在这个时间跨度内,其他一些线程可以打开文件,所以我会得到 IOException。
So, i added extra code for this. In my case i want load XDocument:
所以,我为此添加了额外的代码。就我而言,我想加载 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!!!!!");
}
}
What do you think? Can i change some thing? Maybe i did not have to use IsFileBeingUsed function at all?
你怎么认为?我可以改变一些东西吗?也许我根本不必使用 IsFileBeingUsed 函数?
Thanks
谢谢
回答by Jeremy Thompson
Use this to check if a file is locked:
使用它来检查文件是否被锁定:
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;
}
}
For performance reasons I recommend you read the file content in the same operation. Here are some examples:
出于性能原因,我建议您在同一操作中读取文件内容。这里有些例子:
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;
}
Try it out yourself:
自己试试:
byte[] output1 = Helper.ReadFileBytes(@"c:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");
回答by atlaste
In my experience, you usually want to do this, then 'protect' your files to do something fancy and then use the 'protected' files. If you have just one file you want to use like this, you can use the trick that's explained in the answer by Jeremy Thompson. However, if you attempt to do this on lots of files (say, for example when you're writing an installer), you're in for quite a bit of hurt.
根据我的经验,您通常想要这样做,然后“保护”您的文件以做一些花哨的事情,然后使用“受保护”的文件。如果您只想像这样使用一个文件,则可以使用 Jeremy Thompson 的答案中解释的技巧。但是,如果您尝试对大量文件执行此操作(例如,当您编写安装程序时),您会受到相当大的伤害。
A very elegant way this can be solved is by using the fact that your file system will not allow you to change a folder name if one of the files there it's being used. Keep the folder in the same file system and it'll work like a charm.
解决此问题的一种非常优雅的方法是使用这样一个事实:如果正在使用其中一个文件,则您的文件系统将不允许您更改文件夹名称。将文件夹保存在同一个文件系统中,它会像魅力一样工作。
Do note that you should be aware of the obvious ways this can be exploited. After all, the files won't be locked. Also, be aware that there are other reasons that can result in your Move
operation to fail. Obviously proper error handling (MSDN) can help out here.
请注意,您应该意识到可以利用它的明显方式。毕竟,文件不会被锁定。此外,请注意还有其他原因可能导致您的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);
}
For individual files I'd stick with the locking suggestion posted by Jeremy Thompson.
对于单个文件,我会坚持 Jeremy Thompson 发布的锁定建议。
回答by kernowcode
Just use the exception as intended. Accept that the file is in use and try again, repeatedly until your action is completed. This is also the most efficient because you do not waste any cycles checking the state before acting.
只需按预期使用异常。接受该文件正在使用并重试,重复直到您的操作完成。这也是最有效的,因为您不会在操作前浪费任何检查状态的周期。
Use the function below, for example
使用下面的函数,例如
TimeoutFileAction(() => { System.IO.File.etc...; return null; } );
Reusable method that times out after 2 seconds
2 秒后超时的可重用方法
private T TimeoutFileAction<T>(Func<T> func)
{
var started = DateTime.UtcNow;
while ((DateTime.UtcNow - started).TotalMilliseconds < 2000)
{
try
{
return func();
}
catch (System.IO.IOException exception)
{
//ignore, or log somewhere if you want to
}
}
return default(T);
}