我正在用c#写一个程序,需要反复访问1个图像文件。大多数时候它是有效的,但如果我的计算机运行得很快,它会在文件保存回文件系统之前尝试访问该文件,并抛出一个错误:
文件正在被另一个进程使用
我想找到一个解决这个问题的方法,但我在谷歌上的所有搜索都只能通过使用异常处理来创建检查。这违背了我的宗教信仰,所以我想知道有没有更好的方法?
我正在用c#写一个程序,需要反复访问1个图像文件。大多数时候它是有效的,但如果我的计算机运行得很快,它会在文件保存回文件系统之前尝试访问该文件,并抛出一个错误:
文件正在被另一个进程使用
我想找到一个解决这个问题的方法,但我在谷歌上的所有搜索都只能通过使用异常处理来创建检查。这违背了我的宗教信仰,所以我想知道有没有更好的方法?
当前回答
我很有兴趣看看这是否会引发任何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的问题范围,即简单地检查文件是否在使用,但这确实是我到达这里时希望解决的问题,所以也许它对其他人有用。
其他回答
只要按预期使用异常即可。接受该文件正在使用,并再次尝试,直到操作完成。这也是最有效的,因为您不需要在执行之前浪费任何检查状态的周期。
例如,使用下面的函数
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);
}
更新了该解决方案的说明:使用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;
}
我所知道的唯一方法是使用Win32独占锁API,这不是太快,但例子存在。
对于大多数人来说,一个简单的解决方案就是尝试/捕捉/睡眠循环。
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
}
}
下面是一些代码,据我所知,它与接受的答案相同,但代码更少:
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);
}