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

我正在用C#编写一个需要重复访问1个图像文件的程序。 大多数情况下,它可以工作,但是如果我的电脑运行的很快,它会在文件保存回文件系统之前尝试访问文件,并抛出一个错误: “正被另一个进程使用的文件”

我想find一个解决这个问题的方法,但是我所有的Googlesearch都只能通过使用exception处理来创build检查。 这是违背我的宗教的,所以我想知道有没有人有更好的方法呢?

已更新此解决scheme的注意事项 :使用FileAccess.ReadWrite检查将只读文件失败,因此解决scheme已被修改以检查FileAccess.Read 。 虽然此解决scheme的工作原理是尝试使用FileAccess.Read进行检查,但如果文件上有Write或Read锁,将会失败,但是,如果文件没有写入或读取locking,则此解决scheme将不起作用,即已被打开(用于阅读或写作)与FileShare.Read或FileShare.Write访问。

原文:我在过去的几年中使用过这个代码,而且我没有遇到任何问题。

理解你对使用exception的犹豫,但是你无法一直回避:

 protected virtual bool IsFileLocked(FileInfo file) { FileStream stream = null; try { stream = file.Open(FileMode.Open, FileAccess.Read, 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; } 

你可能会遭受线程竞争的情况,这是有logging的例子被用作安全漏洞。 如果您检查该文件是否可用,但是尝试使用该文件,那么您可以在那个时候抛出恶意用户可以用来在您的代码中强制使用的漏洞。

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

 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. } 

用它来检查文件是否被locking:

 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; } } 

出于性能方面的原因,我build议您在相同的操作中阅读文件内容。 这里有些例子:

 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"); 

这是一个被接受的答案的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并观察Changed事件。

我自己也没用过,但可能值得一试。 如果在这种情况下文件systemwatcher变得有点沉重,我会去try / catch / sleep循环。

我知道唯一的方法是使用Win32独占lockingAPI不是太快,但存在的例子。

大多数人为了简单的解决这个问题,只是为了尝试/捕获/hibernate循环。

你可以返回一个任务给你一个stream,只要它变得可用。 这是一个简化的解决scheme,但这是一个很好的起点。 这是线程安全的。

 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(); } } 

您可以像往常一样使用这个stream:

 using (var stream = await FileStreamGetter.GetStreamAsync()) { Console.WriteLine(stream.Length); } 
 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"); 

希望这可以帮助!

上面接受的答案有一个问题,如果文件已被打开以使用FileShare.Read模式进行写入,或者该文件具有只读属性,则该代码将不起作用。 这个修改后的解决scheme最可靠地工作,有两件事情要记住(对于公认的解决scheme也是如此):

  1. 对于以共享模式打开的文件不起作用
  2. 这不考虑线程问题,因此您需要将其locking或单独处理线程问题。

牢记上述内容,这将检查文件是否被locking以便写入locking以防止读取

 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; } 

根据我的经验,你通常要这样做,然后“保护”你的文件做一些奇特的事情,然后使用“受保护的”文件。 如果你只有一个你想要使用的文件,你可以使用Jeremy Thompson在答案中解释的技巧。 但是,如果您尝试在大量文件上执行此操作(例如,当您正在编写安装程序时),则会受到相当大的伤害。

可以解决的一个非常优雅的方式是通过使用文件系统不允许您更改文件夹名称的事实,如果它正在使用的文件之一。 保持文件夹在同一个文件系统,它会像魅力一样工作。

请注意,您应该意识到可以被利用的明显方式。 毕竟,这些文件不会被locking。 此外,请注意,还有其他原因可能导致您的Move操作失败。 显然适当的error handling(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); } 

对于个人档案,我会坚持由杰里米汤普森张贴locking的build议。

只要按照预期使用例外。 接受该文件正在使用中,并重复尝试,直到您的操作完成。 这也是最有效率的,因为在行动之前不要浪费任何周期来检查状态。

例如,使用下面的函数

 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); } 

下面是一些代码,只要我能够最好的说出与接受的答案相同的东西,但代码less一些:

  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); } 

尝试并移动/复制文件到一个临时目录。 如果可以的话,它没有锁,你可以安全地工作在临时目录,而不会locking。 否则试着在x秒内再次移动它。

您可以使用我的图书馆从多个应用程序访问文件。

你可以从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方法只有在可以locking此对象的专用文件时才会返回true。 但是上传文件的应用程序也必须在文件锁中进行。 如果对象不可访问,metod返回false。

我使用这种解决方法,但我有一个时间之间,当我检查文件locking与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函数?

谢谢

你听说过一个西格顿class吗? 如果你强制所有的图像操作通过这个类,并排队所有的函数调用,你将有primefaces性。 唯一的问题是你需要在一个进程中宿主类。

我有一个类似的问题,并做了一些似乎工作,它正在使用exception处理,但…

我放了一个柜台,继续尝试100次,以阻止无尽的循环。

见下文…

  private void uploadFiles(string filename) { try { string fromFileAndPath = Properties.Settings.Default.Path + "\\" + filename; string toFileAndPath = Properties.Settings.Default.CopyLocation + "\\" + filename; if (!File.Exists(toFileAndPath)) { FileInfo imgInfo = new FileInfo(fromFileAndPath); bool copied = false; int counter = 0; while (!copied && counter < 100) //While was added as I was getting "The process cannot access the file because it is being used by another process" errors. { try { counter++; imgInfo.CopyTo(toFileAndPath); copied = true; } catch { //If it cannot copy catch } } if (counter > 100) throw new Exception("Unable to copy file!"); Thread.Sleep(1); } } catch (Exception ex) { MessageBox.Show("An error occurred: " + ex.Message, "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error); } }