在繁忙的环境中可靠的文件保存(File.Replace)

时间:2012-01-22 00:52:54

标签: c# .net windows filesystems

我正在研究定期需要将数据保存到磁盘的服务器软件。我需要确保旧文件被覆盖,并且在意外情况下文件不会被破坏(例如,仅部分覆盖)。

我采用了以下模式:

string tempFileName = Path.GetTempFileName();
// ...write out the data to temporary file...
MoveOrReplaceFile(tempFileName, fileName);

......其中MoveOrReplaceFile是:

public static void MoveOrReplaceFile( string source, string destination ) {
    if (source == null) throw new ArgumentNullException("source");
    if (destination == null) throw new ArgumentNullException("destination");
    if (File.Exists(destination)) {
        // File.Replace does not work across volumes
        if (Path.GetPathRoot(Path.GetFullPath(source)) == Path.GetPathRoot(Path.GetFullPath(destination))) {
            File.Replace(source, destination, null, true);
        } else {
            File.Copy(source, destination, true);
        }
    } else {
        File.Move(source, destination);
    }
}

只要服务器具有对文件的独占访问权限,这就可以正常工作。但是,File.Replace似乎对外部文件访问非常敏感。每当我的软件在具有防病毒或实时备份系统的系统上运行时,随机的File.Replace错误就会弹出:

  

System.IO.IOException:无法删除要替换的文件。

以下是我已消除的一些可能原因:

  • 未发布的文件句柄:using()可确保尽快释放所有文件句柄。
  • 线程问题:lock()保护对每个文件的所有访问权。
  • 不同的磁盘卷:跨磁盘卷使用时,File.Replace()会失败。我的方法已经检查了这一点,然后回退到File.Copy()。

以下是我遇到的一些建议,以及为什么我不想使用它们:

  • 卷影复制服务:只有在有问题的第三方软件(备份和防病毒监视器等)也使用VSS时,此功能才有效。使用VSS需要大量的P / Invoke,并且具有特定于平台的问题。
  • 锁定文件:在C#中,锁定文件需要保持FileStream打开。它会保留第三方软件,但1)我仍然无法使用File.Replace替换文件,2)就像我上面提到的,我宁愿先写一个临时文件,以免意外损坏。

我很感激任何关于每次都使File.Replace工作的输入,或者更一般地说,可靠地保存/覆盖磁盘上的文件。

3 个答案:

答案 0 :(得分:26)

确实想要使用第3个参数,即备份文件名。这允许Windows简单地重命名原始文件而不必删除它。如果任何其他进程在没有删除共享的情况下打开文件,则删除将失败,重命名绝不是问题。然后,您可以在Replace()调用后自行删除它并忽略错误。同时在Replace()调用之前将其删除,这样重命名不会失败,并且您将清除早期尝试失败。粗略地说:

string backup = destination + ".bak";
File.Delete(backup);
File.Replace(source, destination, backup, true);
try {
    File.Delete(backup);
}
catch {
    // optional:
    filesToDeleteLater.Add(backup);
}

答案 1 :(得分:2)

有几种可能的方法,其中一些是:

  1. 使用“锁定”文件 - 在操作之前创建的临时文件,并指示其他编写者(或读者)正在修改文件并因此将其独占锁定。操作完成后 - 删除锁定文件。此方法假定文件创建命令是原子的。
  2. 使用NTFS事务API(如果适用)。
  3. 创建指向该文件的链接,以随机名称(例如Guid.NewGuid())写入更改的文件 - 然后将链接重新映射到新文件。所有读者都将通过链接访问该文件(该名称已知)。
  4. 当然,所有3种方法都有其自身的缺点和优点

答案 2 :(得分:1)

如果软件正在写入NTFS分区,请尝试使用Transactional NTFS。您可以使用AlphFS作为API的.NET包装器。这可能是编写文件和防止腐败的最可靠方式。