我正在用c#写一个程序,需要反复访问1个图像文件。大多数时候它是有效的,但如果我的计算机运行得很快,它会在文件保存回文件系统之前尝试访问该文件,并抛出一个错误:
文件正在被另一个进程使用
我想找到一个解决这个问题的方法,但我在谷歌上的所有搜索都只能通过使用异常处理来创建检查。这违背了我的宗教信仰,所以我想知道有没有更好的方法?
我正在用c#写一个程序,需要反复访问1个图像文件。大多数时候它是有效的,但如果我的计算机运行得很快,它会在文件保存回文件系统之前尝试访问该文件,并抛出一个错误:
文件正在被另一个进程使用
我想找到一个解决这个问题的方法,但我在谷歌上的所有搜索都只能通过使用异常处理来创建检查。这违背了我的宗教信仰,所以我想知道有没有更好的方法?
也许您可以使用FileSystemWatcher并观察“已更改”事件。
我自己还没用过这个,但也许值得一试。如果对于这种情况,文件系统监控器显得有点重,那么我将使用try/catch/sleep循环。
您可能会遇到线程竞争条件,有文档示例将此用作安全漏洞。如果您检查文件是否可用,但随后尝试使用它,则可能会在此时抛出,恶意用户可能会使用它来强制和利用您的代码。
最好的方法是try catch / finally,它尝试获取文件句柄。
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.
}
更新了该解决方案的说明:使用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;
}
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");
希望这能有所帮助!
我使用这个解决方案,但我有一个时间间隔,当我检查文件锁定与isfilellocked函数和当我打开文件。在这个时间跨度内,其他线程可以打开这个文件,所以我将得到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");
根据我的经验,你通常想这样做,然后“保护”你的文件做一些奇特的事情,然后使用“保护”文件。如果你只想这样使用一个文件,你可以使用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发布的锁定建议。
只要按预期使用异常即可。接受该文件正在使用,并再次尝试,直到操作完成。这也是最有效的,因为您不需要在执行之前浪费任何检查状态的周期。
例如,使用下面的函数
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写入。读取模式或如果文件具有只读属性,代码将无法工作。这个修改后的解决方案最可靠,需要记住两件事(对于公认的解决方案也是如此):
它将不适用于已以写共享模式打开的文件 这没有考虑到线程问题,所以你需要锁定它或单独处理线程问题。
记住上面的内容,这将检查文件是被写入锁定还是被阻止读取锁定:
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);
}
下面是一些代码,据我所知,它与接受的答案相同,但代码更少:
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。
我很有兴趣看看这是否会引发任何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(s)。我可能还会在启动时清除应用程序存储,因为这些文件是临时的。
我意识到这超出了OP的问题范围,即简单地检查文件是否在使用,但这确实是我到达这里时希望解决的问题,所以也许它对其他人有用。
除了工作3-liner,只是为了参考:如果你想要完整的信息-在微软开发中心有一个小项目:
https://code.msdn.microsoft.com/windowsapps/How-to-know-the-process-704839f4
现发现于: https://github.com/TacticalHorse/LockFinder/blob/master/LockFinder.cs
引言:
The C# sample code developed in .NET Framework 4.0 would help in finding out which is the process that is having a lock on a file. RmStartSession function which is included in rstrtmgr.dll has been used to create a restart manager session and according to the return result a new instance of Win32Exception object is created. After registering the resources to a Restart Manager session via RmRegisterRescources function, RmGetList function is invoked to check what are the applications are using a particular file by enumerating the RM_PROCESS_INFO array.
它通过连接到“重启管理器会话”来工作。
重启管理器使用向会话注册的资源列表 确定哪些应用程序和服务必须关闭并重新启动。 资源可以通过文件名、服务短名称或 描述正在运行的应用程序的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)
{
}
}
I once needed to upload PDFs to an online backup archive. But the backup would fail if the user had the file open in another program (such as PDF reader). In my haste, I attempted a few of the top answers in this thread but could not get them to work. What did work for me was trying to move the PDF file to its own directory. I found that this would fail if the file was open in another program, and if the move were successful there would be no restore-operation required as there would be if it were moved to a separate directory. I want to post my basic solution in case it may be useful for others' specific use cases.
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。
在这里,微软描述了以下检查IOException是否由于锁定文件导致的方法:
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32 ) {
Console.WriteLine("There is a sharing violation.");
}