有没有办法检查文件是否正在使用?

我正在用C#编写一个程序,需要重复访问1个图像文件。大多数时候它都可以工作,但是如果我的计算机运行速度很快,它会在文件保存回文件系统之前尝试访问该文件并抛出错误:

“文件正在被另一个进程使用”

我想找到一种方法来解决这个问题,但是我所有的谷歌搜索都只产生了通过使用异常处理来创建检查。这违背了我的宗教信仰,所以我想知道是否有人有更好的方法来做到这一点?

668686 次浏览

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

大多数人,对于一个简单的解决方案,只是尝试/catch/睡眠循环。

也许您可以使用监控文件并观察Changed事件。

我自己没有用过这个,但可能值得一试。如果文件系统观察器在这种情况下有点重,我会选择try/catch/睡眠循环。

您可能会因此遭受线程竞争条件的影响,其中有记录在案的示例将其用作安全漏洞。如果您检查该文件是否可用,但随后尝试使用它,您可能会在该点抛出,恶意用户可能会利用该漏洞在您的代码中强制和利用。

您最好的选择是尝试捕获/最终尝试获取文件句柄。

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秒内再次移动它。

关于此解决方案的更新说明:使用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 lockedreturn false;}
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 checkingif (isFileInUse)Console.WriteLine("File is in use");elseConsole.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!!!!!");}}

你怎么想?我可以改变一些东西吗?也许我根本不需要使用IsFileBeing的功能?

谢了

使用它来检查文件是否被锁定:

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 exceptiontry {//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 filereturn 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 lengthbuffer = new byte[length];            // create bufferint count;                            // actual number of bytes readint 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 lengthbuffer = new byte[length];            // create bufferint count;                            // actual number of bytes readint 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 youfileContents = 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 lengthbuffer = new byte[length];            // create bufferint count;                            // actual number of bytes readint 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");

根据我的经验,你通常想这样做,然后“保护”你的文件做一些花哨的事情,然后使用“受保护”的文件。如果你只想这样使用一个文件,你可以使用Jeremy Thompson在回答中解释的技巧。然而,如果你试图在很多文件上这样做(例如,当你编写安装程序时),你会受到很大的伤害。

解决这个问题的一个非常优雅的方法是使用这样一个事实,即如果其中一个文件正在使用,您的文件系统将不允许您更改文件夹名称。将文件夹保存在同一个文件系统中,它会像魅力一样工作。

请注意,您应该知道可以利用此漏洞的明显方式。毕竟,文件不会被锁定。此外,请注意还有其他原因可能导致您的Move操作失败。显然,适当的错误处理(MSDN)可以在这里提供帮助。

var originalFolder = @"c:\myHugeCollectionOfFiles"; // your folder name herevar 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发布的锁定建议。

只需按预期使用异常。接受文件正在使用中,然后重复重试,直到您的操作完成。这也是最有效的,因为您不会浪费任何周期在操作前检查状态。

使用下面的函数,例如

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模式打开文件进行写入,或者如果文件具有只读属性,则代码将无法工作。此修改后的解决方案工作最可靠,但要记住两件事(对于接受的解决方案也是如此):

  1. 它不适用于以写共享模式打开的文件
  2. 这没有考虑到线程问题,因此您需要将其锁定或单独处理线程问题。

记住上述内容,这将检查文件是写入锁定还是锁定以防止读取

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 lockedfs = 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 modetry{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);}

以下是一些代码,据我所知,它们与公认的答案相同,但代码较少:

    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 Xable. 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。

我很想知道这是否会触发任何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);}}

可能可以对catch块给予更多的关注,以确保我捕获了正确的IOException。我可能还会在启动时清除应用程序存储,因为这些文件无论如何都是临时的。

我意识到这超出了OP简单检查文件是否正在使用的问题的范围,但这确实是我到达这里时想要解决的问题,所以也许它对其他人有用。

除了工作3行代码和仅供参考:如果您想要全面信息-Microsoft Dev Center上有一个小项目:

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#示例代码将有助于找出哪个进程对文件有锁。RmStart会话开始时间包含在rstrtmgr.dll中的函数用于创建重启管理器会话,并根据返回结果创建了Win32Exception对象的新实例。之后通过将资源注册到重新启动管理器会话资源注册资源函数,调用获取资源列表函数检查通过枚举哪些应用程序正在使用特定文件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}}

我最近遇到了这个问题,发现了这个: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.");}