我们通过将新记录写入临时文件,然后用临时文件替换旧文件来执行大型文本文件的更新。一个严重缩写的版本:
var tpath = Path.GetTempFileName();
try
{
using (var sf = new StreamReader(sourcepath))
using (var tf = new StreamWriter(tpath))
{
string line;
while ((line = sf.ReadLine()) != null)
tf.WriteLine(UpdateLine(line));
}
File.Delete(sourcepath);
File.Move(tpath, sourcepath);
}
catch
{
File.Delete(tpath);
throw;
}
如果有异常抛出异常(找不到文件,没有权限),原始文件保持不变,这就是我们想要的。
但是,代码存在以下问题:
是否存在Delete
有效但Move
失败的真实情况?这将删除原始数据和更新数据。这很糟糕。
最常见的失败是源文件从另一个应用程序打开,Delete
失败。这意味着将丢弃所有更新工作。有没有办法查看源文件在开始时是否可删除,如果没有则放弃更新?
我们让用户将Windows资源管理器摘要属性(如标题或注释)放在文件上。删除文件时会丢弃这些内容。有没有办法将旧文件的Summary属性复制到新文件?我们应该这样做吗?
答案 0 :(得分:5)
避免“删除然后移动失败问题”的正常方法是:
然后当你来阅读时,如果缺少file.current,则使用file.new,如果你看到则删除file.old。
检查文件是否可用:尝试将其打开以进行写入,但追加到最后。当然,你需要在移动它之前关闭手柄,而在其他人之间可以打开它 - 但它至少会是合理的优化。
不确定复制摘要等,我很害怕。
答案 1 :(得分:2)
为什么不先尝试检查FileAttributes?
尝试这样的事情:
//If File is readonly
if ( (file.Attribute & System.FileAttributes.ReadOnly) == System.FileAttributes.ReadOnly )
//Don't delete.
也可以尝试使用.OpenWrite()。如果您可以打开要写入的文件,则不会访问该文件,并且当前未使用该文件。如果文件当前处于未打开状态,则只能打开文件进行写入。我不推荐这个,但它可以帮到你。
FileStream fs = File.OpenWrite(file);
fs.Close();
return false;
您还可以使用FileLock检查方法。像这样:
protected virtual bool IsFileLocked(FileInfo file)
{
try
{
using (file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
return false;
}
}
catch (IOException)
{
return true;
}
}
您可能还想检查FileIOPermission.Write。这允许查看文件是否可写(并且能够删除)。
fileIOPerm = New FileIOPermission(FileIOPermissionAccess.Write, FileSpec);
fileIOPerm.Demand();
关于原帖中的问题#3 ...您始终可以使用File.Copy(path1,path2,true)将文件移动到临时文件夹。您可能需要考虑使用临时文件夹并为文件操作编写更好的逻辑。
如果你决定使用临时文件夹或临时文件/中间文件,那么你也可以修复你的问题#2。尝试先移动文件。
答案 2 :(得分:1)
一些“肮脏”的伎俩。
首先不要删除原始文件,先将其移动到另一个位置(临时路径),然后如果移动更新的文件成功则删除旧文件。如果更新失败,您可以在原处恢复文件。
我认为本文将帮助您MSDN
如果用户需要“标题”和“评论”,您应该保留它们。我从来没有尝试过将它们从一个文件复制到另一个文件,所以我不知道如何帮助你。
答案 3 :(得分:1)
Transactional NTFS可能对您的方案有用。
答案 4 :(得分:1)
正如已经提到的,你真的应该调查ReplaceFile,它旨在帮助你正在做的事情。 .NET函数只是Win32函数的一个包装器,人们可能希望原子性问题得到解决。
答案 5 :(得分:1)
很多好的建议。我能够用以下方法解决问题:
var sInfo = new FileInfo(sourcePath);
if (sInfo.IsReadOnly)
throw new IOException("File '" + sInfo.FullName + "' is read-only.");
var tPath = Path.GetTempFileName();
try
{
// This throws if sourcePath does not exist, is opened, or is not readable.
using (var sf = sInfo.OpenText())
using (var tf = new StreamWriter(tPath))
{
string line;
while ((line = sf.ReadLine()) != null)
tf.WriteLine(UpdateLine(line));
}
string backupPath = sInfo.FullName + ".bak";
if (File.Exists(backupPath))
File.Delete(backupPath);
File.Move(tPath, backupPath);
tPath = backupPath;
File.Replace(tPath, sInfo.FullName, null);
}
catch (Exception ex)
{
File.Delete(tPath);
throw new IOException("File '" + sInfo.FullName + "' could not be overwritten.", ex);
}
如果源文件是打开的或不可读的,则会抛出 OpenText
,并且未完成更新。如果有任何抛出,原始文件保持不变。 Replace
将旧文件的“摘要”属性复制到新文件中。即使源文件与临时文件夹位于不同的卷上,也可以使用此功能。
答案 6 :(得分:0)
我发现将这个模式包装在它自己的类中很有用。
class Program {
static void Main( string[] args ) {
using( var ft = new FileTransaction( @"C:\MyDir\MyFile.txt" ) )
using( var sw = new StreamWriter( ft.TempPath ) ) {
sw.WriteLine( "Hello" );
ft.Commit();
}
}
}
public class FileTransaction :IDisposable {
public string TempPath { get; private set; }
private readonly string filePath;
public FileTransaction( string filePath ) {
this.filePath = filePath;
this.TempPath = Path.GetTempFileName();
}
public void Dispose() {
if( TempPath != null ) {
try {
File.Delete( TempPath );
}
catch { }
}
}
public void Commit() {
try {
var oldPath = filePath + ".old";
File.Move( filePath, oldPath );
}
catch {}
File.Move( TempPath, filePath );
TempPath = null;
}
}
答案 7 :(得分:0)
此代码段显示了一种获取文件独占访问权限的技术(在本例中为读取):
// Try to open a file exclusively
FileInfo fi = new FileInfo(fullFilePath);
int attempts = maxAttempts;
do
{
try
{
// Try to open for reading with exclusive access...
fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.None);
}
// Ignore any errors...
catch { }
if (fs != null)
{
break;
}
else
{
Thread.Sleep(100);
}
}
while (--attempts > 0);
// Did we manage to open file exclusively?
if (fs != null)
{
// use open file....
}