我正在用C#编写一个程序,该程序须要重复访问1个图像文件。 在大多数状况下,它均可以工做,可是若是个人计算机运行速度很快,它将在尝试将文件保存回文件系统以前尝试访问该文件,并抛出错误: “文件正在被另外一个进程使用” 。 安全
我想找到一种解决方法,可是我全部的Google搜索都只能经过使用异常处理来建立检查。 这与个人宗教信仰背道而驰,因此我想知道是否有人能作得更好? less
使用此命令检查文件是否被锁定: async
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; }
本身尝试一下: ui
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的答案中解释的技巧。 可是,若是您尝试对许多文件执行此操做(例如,当您编写安装程序时),则可能会遭受不少伤害。 this
解决这个问题的一种很是优雅的方法是使用如下事实:若是文件系统中的文件之一正在使用,则您的文件系统将不容许您更改其名称。 将文件夹保留在同一文件系统中,它将像超级按钮同样工做。 spa
请注意,您应该意识到能够利用此漏洞的明显方法。 毕竟,文件不会被锁定。 另外,请注意还有其余缘由可能致使“ 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发表的锁定建议。 code
只需按预期使用异常便可。 接受该文件正在使用中,而后重试,直到操做完成。 这也是最有效的方法,由于您在执行操做以前不会浪费任什么时候间检查状态。 orm
例如,使用如下功能
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); }