c# - File.Replace 抛出 ERROR_UNABLE_TO_MOVE_REPLACEMENT

标签 c# io filesystems

我有一个“安全”的配置文件保存例程,它在将用户配置数据写入磁盘时尝试保持原子性,避免磁盘缓存等。

代码是这样的:

public static void WriteAllTextSafe(string path, string contents)
{
    // generate a temp filename
    var tempPath = Path.GetTempFileName();

    // create the backup name
    var backup = path + ".backup";


    // delete any existing backups
    if (File.Exists(backup))
        File.Delete(backup);

    // get the bytes
    var data = Encoding.UTF8.GetBytes(contents);

    if (File.Exists(path))
    {
        // write the data to a temp file
        using (var tempFile = File.Create(tempPath, 4096, FileOptions.WriteThrough))
            tempFile.Write(data, 0, data.Length);

        // replace the contents
        File.Replace(tempPath, path, backup, );
    }
    else
    {
        // if the file doesn't exist we can't replace so just write it
        using (var tempFile = File.Create(path, 4096, FileOptions.WriteThrough))
            tempFile.Write(data, 0, data.Length);
    }
}

在大多数系统上,这都能完美运行,我没有收到任何问题报告,但对于某些用户来说,每次我的程序调用此函数时,他们都会收到以下错误:

System.IO.IOException: 置換するファイルを置換されるファイルに移動できません。置換されるファイルの名前は、元のままです。
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.File.InternalReplace(String sourceFileName, String destinationFileName, String destinationBackupFileName, Boolean ignoreMetadataErrors)
    at download.ninja.BO.FileExtensions.WriteAllTextSafe(String path, String contents)
    at download.ninja.BO.FileExtensions.SaveConfig(String path, Object toSave)

经过一些调查并在谷歌翻译的帮助下,我发现实际错误是从 wine32 ReplaceFile 函数中抛出的:

ERROR_UNABLE_TO_MOVE_REPLACEMENT 1176 (0x498)
The replacement file could not be renamed. If lpBackupFileName was specified, the replaced and
replacement files retain their original file names. Otherwise, the replaced file no longer exists 
and the replacement file exists under its original name.

http://msdn.microsoft.com/en-us/library/windows/desktop/aa365512(v=vs.85).aspx

问题是我不知道为什么 Windows 会抛出这个错误。我已经尝试在本地将文件设置为只读,但这会抛出 Unauthorized 异常而不是 IOException,所以我不认为这是导致问题的原因。

我的第二个猜测是不知何故被锁定了,但我只有一个用于读取所有配置文件的读取函数,并且应该在完成时关闭所有文件句柄

public static T LoadJson<T>(string path)
{
    try
    {
        // load the values from the file
        using (var r = new StreamReader(path))
        {
            string json = r.ReadToEnd();
            T result = JsonConvert.DeserializeObject<T>(json);

            if (result == null)
                return default(T);

            return result;
        }
    }
    catch (Exception)
    {
    }

    return default(T);
}

我也曾尝试在 LoadJson 函数中抛出假异常来尝试锁定文件,但我似乎做不到。

即便如此,我也尝试通过在不同的进程中打开文件并在文件仍处于打开状态时运行保存代码来模拟文件锁定,这会产生不同的(预期的)错误:

System.IO.IOException: The process cannot access the file because it is being used by another process.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.File.InternalReplace(String sourceFileName, String destinationFileName, String destinationBackupFileName, Boolean ignoreMetadataErrors)
    at download.ninja.BO.FileExtensions.WriteAllTextSafe(String path, String contents) 

所以问题是.. 是什么导致 Windows 在某些系统上抛出此 ERROR_UNABLE_TO_MOVE_REPLACEMENT 错误

注意:我的程序每次都会抛出此错误,而不仅仅是偶尔。

最佳答案

OK,问题找到了。似乎传递到 ReplaceFile 的文件必须位于 SAME DRIVE

在这种情况下,用户将他们的临时文件夹更改为 d:\tmp\

所以 File.Replace 看起来像这样:

File.Replace(@"D:\tmp\sometempfile", @"c:\AppData\DN\app-config", @"c:\AppData\DN\app-config.backup");

File.Replace 中源文件和目标文件之间的驱动差异似乎是导致问题的原因。

我修改了我的 WriteAllTextSafe功能如下,我的用户报告问题已解决!

public static void WriteAllTextSafe(string path, string contents)
{
    // DISABLED: User temp folder might be on different drive
    // var tempPath = Path.GetTempFileName();

    // use the same folder so that they are always on the same drive!
    var tempPath = Path.Combine(Path.GetDirectoryName(path), Guid.NewGuid().ToString());

    ....
}

据我所知,没有文档说明这两个文件需要位于同一驱动器上,但我可以通过在 File.Replace 中手动输入不同的驱动器(但有效路径)来重现该问题。

关于c# - File.Replace 抛出 ERROR_UNABLE_TO_MOVE_REPLACEMENT,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25687713/

相关文章:

unit-testing - Golang - 使用文件系统进行测试并达到 100%

c# - 如何在 PreInit 期间访问 ASP.NET 页面控件?

c++ - printf 与 cstdio 和 iostream 在汇编级别上的差异

android - 检测在 Android 中工作的文件系统

c# - 使用点分隔符将 double 写入文本文件

Java:使用文本文件

PHP mkdir( $recursive = true ) 跳过最后一个目录

c# - 在 C# 中延迟任务 - 释放数据库上下文

c# - 获取一个月中的星期六

C#修改Excel文件,保持样式