我正在用C#编写一个程序,该程序需要重复访问1个图像文件。在大多数情况下,它都可以工作,但是如果我的计算机运行速度很快,它将在尝试将文件保存回文件系统之前尝试访问该文件,并抛出错误:“文件正在被另一个进程使用”。
我想找到一种解决方法,但是我所有的Google搜索都只能通过使用异常处理来创建检查。这与我的宗教信仰背道而驰,所以我想知道是否有人能做得更好?
我正在用C#编写一个程序,该程序需要重复访问1个图像文件。在大多数情况下,它都可以工作,但是如果我的计算机运行速度很快,它将在尝试将文件保存回文件系统之前尝试访问该文件,并抛出错误:“文件正在被另一个进程使用”。
我想找到一种解决方法,但是我所有的Google搜索都只能通过使用异常处理来创建检查。这与我的宗教信仰背道而驰,所以我想知道是否有人能做得更好?
Answers:
关于此解决方案的更新的注释:FileAccess.ReadWrite
只读文件的“ 检查” 将失败,因此该解决方案已被修改为“”进行检查FileAccess.Read
。尽管此解决方案有效,因为尝试检查是否FileAccess.Read
会失败(如果文件上具有写入或读取锁定),但是,如果文件没有写入或读取锁定(即已打开),则此解决方案将不起作用(用于读取或写入)具有FileShare.Read或FileShare.Write访问权限。
原文: 我在过去的几年中一直使用此代码,但至今没有任何问题。
了解您对使用异常的犹豫,但是您无法始终避免使用它们:
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) {/*...*/}
。
您可能因此而遭受线程争用的情况,有记录在此的示例被用作安全漏洞。如果您检查文件是否可用,然后尝试使用它,则可能会丢掉该文件,恶意用户可能会使用该文件来强制和利用您的代码。
最好的选择是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.
}
使用此命令检查文件是否被锁定:
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
人总是孤独的。只需将两者交换即可。
只需按预期使用异常即可。接受该文件正在使用中,然后重试,直到操作完成。这也是最有效的方法,因为您在执行操作之前不会浪费任何时间检查状态。
例如,使用以下功能
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);
}
也许您可以使用FileSystemWatcher来监视Changed事件。
我自己没有用过,但是可能值得一试。如果在这种情况下filesystemwatcher有点沉重,我将尝试使用try / catch / sleep循环。
您可以返回一个任务,该任务将在流可用时立即为您提供流。这是一个简化的解决方案,但这是一个很好的起点。这是线程安全的。
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()
?
我知道的唯一方法是使用Win32独占锁定API,该API不太快,但是存在示例。
为了解决这个问题,大多数人只是尝试尝试/捕获/睡眠循环。
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模式进行写入,或者如果文件具有“只读”属性,则代码将不起作用。修改后的解决方案最可靠地工作,需要牢记两点(对于公认的解决方案也是如此):
牢记以上几点,这将检查文件是被锁定以进行写入还是被锁定以防止读取:
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;
}
除了可以使用的3层衬纸,仅供参考:如果您需要完整的信息-Microsoft Dev Center上有一个小项目:
https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4
从简介:
在.NET Framework 4.0中开发的C#示例代码将有助于找出哪个进程锁定了文件。 rstrtmgr.dll中包含的RmStartSession函数已用于创建重新启动管理器会话,并根据返回结果创建Win32Exception对象的新实例。通过RmRegisterRescources函数将资源注册到Restart Manager会话后 ,通过枚举RM_PROCESS_INFO数组,调用RmGetList函数以检查哪些应用程序正在使用特定文件。
通过连接到“重新启动管理器会话”来工作。
重新启动管理器使用在会话中注册的资源列表来确定必须关闭并重新启动哪些应用程序和服务。 可以通过文件名,服务简称或描述正在运行的应用程序的RM_UNIQUE_PROCESS结构来标识资源。
这可能是有点过度设计为您的特定需求。但如果这是你想要的,继续前进,抢VS-项目。
以我的经验,您通常要这样做,然后“保护”文件以做一些花哨的事情,然后使用“受保护”的文件。如果只有这样一个文件要使用,可以使用Jeremy Thompson的答案中解释的技巧。但是,如果您尝试对许多文件执行此操作(例如,当您编写安装程序时),则可能会遭受很多伤害。
解决这个问题的一种非常优雅的方法是使用以下事实:如果文件系统中的文件之一正在使用中,则文件系统将不允许您更改其名称。将文件夹保留在同一文件系统中,它将像超级按钮一样工作。
请注意,您应该意识到可以利用此漏洞的明显方法。毕竟,文件不会被锁定。另外,请注意还有其他原因可能导致您的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发表的锁定建议。
FileShare
并检查了锁。
据我所知,这是一些代码,它与接受的答案具有相同的功能,但是代码更少:
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。但是上传文件的应用也必须在文件锁定中执行。如果无法访问对象,则方法返回false。
我曾经需要将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
}
我很想看看这是否会触发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仅检查文件是否正在使用的问题的范围,但这确实是我到达这里时要解决的问题,因此也许对其他人有用。
这样的帮助吗?
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)
{
}
}
尝试将文件移动/复制到临时目录。如果可以的话,它没有锁,您可以安全地在temp目录中工作而不会获得锁。否则,请尝试在x秒内再次移动它。
我使用这种解决方法,但是当我使用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函数?
谢谢