提问人:Dawsy 提问时间:5/18/2009 最后编辑:Selim YildizDawsy 更新时间:4/10/2023 访问量:705314
有没有办法检查文件是否正在使用中?
Is there a way to check if a file is in use?
问:
我正在用 C# 编写一个程序,需要重复访问 1 个图像文件。大多数情况下,它都可以工作,但是如果我的计算机运行速度很快,它将在文件保存回文件系统之前尝试访问该文件并引发错误:
“文件正在被另一个进程使用”
我想找到解决这个问题的方法,但我所有的谷歌搜索都只能通过使用异常处理来创建检查。这违背了我的宗教信仰,所以我想知道是否有人有更好的方法?
答:
我所知道的唯一方法是使用 Win32 独占锁 API,它不是太快,但存在示例。
大多数人,对于一个简单的解决方案,只需尝试/捕捉/睡眠循环。
评论
也许您可以使用 FileSystemWatcher 并监视 Changed 事件。
我自己没有用过这个,但可能值得一试。如果在这种情况下文件系统观察器有点重,我会选择 try/catch/sleep 循环。
评论
您可能会遇到线程争用条件,有记录在案的示例表明这被用作安全漏洞。如果检查文件是否可用,但随后尝试使用它,则可能会在此时抛出,恶意用户可能会利用该文件来强制和利用您的代码。
你最好的选择是尝试捕捉/最后,它试图获取文件句柄。
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.
}
评论
尝试将文件移动/复制到临时目录。如果可以的话,它没有锁,你可以安全地在临时目录中工作,而不会被锁住。否则,只需尝试在 x 秒内再次移动它。
评论
更新了有关此解决方案的说明:对于只读文件,检查 with 将失败,因此已将解决方案修改为 check with 。FileAccess.ReadWrite
FileAccess.Read
源语言:在过去的几年里,我一直在使用这段代码,我没有遇到任何问题。
理解你对使用异常的犹豫,但你不能一直避免它们:
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;
}
评论
public static bool IsLocked(this FileInfo file) {/*...*/}
static bool FileInUse(string path) {
try {
using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate)) {
return !fs.CanWrite;
}
//return false;
}
catch (IOException ex) {
return true;
}
}
string filePath = "C:\\Documents And Settings\\yourfilename";
bool 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");
希望这有帮助!
评论
我使用此解决方法,但是在使用 IsFileLocked 函数检查文件锁定和打开文件之间有一个时间跨度。在这个时间跨度内,其他一些线程可以打开该文件,所以我会得到 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函数?
谢谢
评论
使用此选项来检查文件是否已锁定:
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:\temp\test.txt");
string output2 = Helper.ReadFileTextWithEncoding(@"c:\temp\test.txt");
string output3 = Helper.ReadFileTextNoEncoding(@"c:\temp\test.txt");
评论
IOException
Exception
IOException
IOException
根据我的经验,您通常希望这样做,然后“保护”您的文件以做一些花哨的事情,然后使用“受保护”文件。如果你只想像这样使用一个文件,你可以使用 Jeremy Thompson 在回答中解释的技巧。但是,如果您尝试在大量文件上执行此操作(例如,当您编写安装程序时),您将受到相当大的伤害。
一个非常优雅的解决方法是利用这样一个事实,即如果您的文件系统中的一个文件正在使用,则不允许您更改文件夹名称。将文件夹保存在同一个文件系统中,它会像魅力一样工作。
请注意,您应该了解可以利用的明显方式。毕竟,文件不会被锁定。此外,请注意,还有其他原因可能导致操作失败。显然,正确的错误处理 (MSDN) 可以在这方面提供帮助。Move
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 发布的锁定建议。
评论
FileShare
只需按预期使用异常即可。接受文件正在使用中,然后重试,重复,直到操作完成。这也是最有效的,因为您不会浪费任何周期在行动之前检查状态。
例如,使用以下函数
TimeoutFileAction(() => { System.IO.File.etc...; return null; } );
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);
}
上面接受的答案会遇到一个问题,即如果已打开文件以使用 FileShare.Read 模式进行写入,或者如果文件具有只读属性,则代码将不起作用。这种修改后的解决方案工作最可靠,需要记住两件事(对于公认的解决方案也是如此):
- 它不适用于使用写入共享模式打开的文件
- 这没有考虑线程问题,因此您需要将其锁定或单独处理线程问题。
请记住上述内容,这将检查文件是被锁定以进行写入,还是被锁定以防止读取:
public static bool FileLocked(string FileName)
{
FileStream fs = null;
try
{
// NOTE: This doesn't handle situations where file is opened for writing by another process but put into write shared mode, it will not throw an exception and won't show it as write locked
fs = File.Open(FileName, FileMode.Open, FileAccess.ReadWrite, FileShare.None); // If we can't open file for reading and writing then it's locked by another process for writing
}
catch (UnauthorizedAccessException) // https://msdn.microsoft.com/en-us/library/y973b725(v=vs.110).aspx
{
// This is because the file is Read-Only and we tried to open in ReadWrite mode, now try to open in Read only mode
try
{
fs = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (Exception)
{
return true; // This file has been locked, we can't even open it to read
}
}
catch (Exception)
{
return true; // This file has been locked
}
finally
{
if (fs != null)
fs.Close();
}
return false;
}
评论
您可以返回一个任务,该任务会在流可用时立即为您提供流。这是一个简化的解决方案,但它是一个很好的起点。它是线程安全的。
private async Task<Stream> GetStreamAsync()
{
try
{
return new FileStream("sample.mp3", FileMode.Open, FileAccess.Write);
}
catch (IOException)
{
await Task.Delay(TimeSpan.FromSeconds(1));
return await GetStreamAsync();
}
}
您可以像往常一样使用此流:
using (var stream = await FileStreamGetter.GetStreamAsync())
{
Console.WriteLine(stream.Length);
}
评论
GetStreamAsync()
以下是一些代码,据我所知,它们与公认的答案相同,但代码更少:
public static bool IsFileLocked(string file)
{
try
{
using (var stream = File.OpenRead(file))
return false;
}
catch (IOException)
{
return true;
}
}
但是,我认为通过以下方式执行此操作会更强大:
public static void TryToDoWithFileStream(string file, Action<FileStream> action,
int count, int msecTimeOut)
{
FileStream stream = null;
for (var i = 0; i < count; ++i)
{
try
{
stream = File.OpenRead(file);
break;
}
catch (IOException)
{
Thread.Sleep(msecTimeOut);
}
}
action(stream);
}
您可以使用我的库从多个应用程序访问文件。
可以从 nuget 安装它:Install-Package Xabe.FileLock
如果您想了解有关它的更多信息,请查看 https://github.com/tomaszzmuda/Xabe.FileLock
ILock fileLock = new FileLock(file);
if(fileLock.Acquire(TimeSpan.FromSeconds(15), true))
{
using(fileLock)
{
// file operations here
}
}
fileLock.Acquire 方法仅在可以锁定此对象独占的文件时才返回 true。 但是上传文件的应用程序也必须在文件锁定中执行此操作。 如果对象无法访问 metod 则返回 false。
评论
using
if
我很想知道这是否会触发任何 WTF 反射。我有一个流程,可以从控制台应用程序创建并随后启动PDF文档。但是,我正在处理一个弱点,即如果用户多次运行该进程,在不首先关闭先前生成的文件的情况下生成相同的文件,则应用程序将抛出异常并死机。这种情况相当频繁,因为文件名基于销售报价单编号。
我没有以这种不优雅的方式失败,而是决定依赖自动递增的文件版本控制:
private static string WriteFileToDisk(byte[] data, string fileName, int version = 0)
{
try
{
var versionExtension = version > 0 ? $"_{version:000}" : string.Empty;
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{fileName}{versionExtension}.pdf");
using (var writer = new FileStream(filePath, FileMode.Create))
{
writer.Write(data, 0, data.Length);
}
return filePath;
}
catch (IOException)
{
return WriteFileToDisk(data, fileName, ++version);
}
}
也许可以对块给予更多的关注,以确保我捕捉到正确的 IOException。我可能还会在启动时清除应用程序存储,因为这些文件无论如何都是临时的。catch
我意识到这超出了 OP 问题的范围,即简单地检查文件是否正在使用中,但这确实是我到达这里时想要解决的问题,所以也许它对其他人有用。
除了工作 3 行之外,仅供参考:如果您想要完整的信息 - Microsoft 开发人员中心上有一个小项目:
https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4
现在位于: https://github.com/TacticalHorse/LockFinder/blob/master/LockFinder.cs
从引言:
在 .NET Framework 4.0 中开发的 C# 示例代码将有助于 找出哪个进程对文件有锁定。包含在 rstrtmgr.dll 中的 RmStartSession 函数已 用于创建重启管理器会话,并根据 结果:创建 Win32Exception 对象的新实例。后 通过 RmRegisterRescources 函数将资源注册到重启管理器会话,调用 RmGetList 函数来检查 哪些应用程序正在通过枚举来使用特定文件 RM_PROCESS_INFO数组。
它通过连接到“重新启动管理器会话”来工作。
重启管理器使用向会话注册的资源列表 确定必须关闭并重新启动哪些应用程序和服务。资源可以通过文件名、服务短名称或 RM_UNIQUE_PROCESS描述正在运行的应用程序的结构。
对于您的特定需求来说,它可能有点过度设计...... 但是,如果这是您想要的,请继续获取 vs-project。
评论
这样的事情会有帮助吗?
var fileWasWrittenSuccessfully = false;
while (fileWasWrittenSuccessfully == false)
{
try
{
lock (new Object())
{
using (StreamWriter streamWriter = new StreamWriter("filepath.txt"), true))
{
streamWriter.WriteLine("text");
}
}
fileWasWrittenSuccessfully = true;
}
catch (Exception)
{
}
}
我曾经需要将 PDF 上传到在线备份存档。但是,如果用户在另一个程序(如 PDF 阅读器)中打开文件,则备份将失败。在匆忙中,我尝试了这个线程中的一些顶级答案,但无法让它们起作用。对我有用的是尝试将 PDF 文件移动到它自己的目录中。我发现,如果文件在另一个程序中打开,这将失败,如果移动成功,则不需要还原操作,因为如果将其移动到单独的目录,则会失败。我想发布我的基本解决方案,以防它可能对其他人的特定用例有用。
string str_path_and_name = str_path + '\\' + str_filename;
FileInfo fInfo = new FileInfo(str_path_and_name);
bool open_elsewhere = false;
try
{
fInfo.MoveTo(str_path_and_name);
}
catch (Exception ex)
{
open_elsewhere = true;
}
if (open_elsewhere)
{
//handle case
}
retry_possibility:
//somecode here
try
{
using(FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
{
stream.Close();
}
//write or open your file here
}
catch (IOException)
{
DialogResult dialogResult = MessageBox.Show("This file is opened by you or another user. Please close it and press retry.\n"+ expFilePath, "File Locked", MessageBoxButtons.RetryCancel);
if (dialogResult == DialogResult.Retry)
{
goto retry_possibility;
}
else if (dialogResult == DialogResult.Cancel)
{
//do nothing
}
}
评论
goto
我最近遇到了这个问题,发现这个:https://learn.microsoft.com/en-us/dotnet/standard/io/handling-io-errors。
在这里,Microsoft 描述了以下检查是否由于锁定文件而导致的方法:IOException
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32 ) {
Console.WriteLine("There is a sharing violation.");
}
评论
(e.HResult & 0x0000FFFF) == 33
评论