File.Replace抛出ERROR_UNABLE_TO_MOVE_REPLACEMENT

时间:2014-09-05 14:02:24

标签: 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会抛出这个错误..我已经尝试将文件设置为只读本地但是抛出了一个未授权的异常而不是IOException所以我不相信这会导致问题

我的第二个猜测是,它以某种方式被锁定,但我只有一个读取函数用于读取所有配置文件,并且应该在finsihes时关闭所有文件句柄

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错误

注意:抛出此错误每次我的程序尝试更换受影响计算机上的文件..而不仅仅是偶尔。

3 个答案:

答案 0 :(得分:6)

好的,所以发现了问题。传递给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中源文件和desitnation文件之间的驱动器差异似乎是造成问题的原因。

我修改了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

答案 1 :(得分:0)

您是否有机会在您的文件名中使用硬编码驱动器,例如: c:\ etc?

如果您是,请不要,它们可能不适用于语言环境为1041(日语)的运行时。

而是使用框架来获取驱动器部件并动态构建路径。

string drivePath = System.IO.Path.GetPathRoot(System.Environment.SystemDirectory);
string somePath = string.Concat(drivePath, "someFolder\SomeFileName.Txt");

答案 2 :(得分:-1)

我在使用OneDrive上的源文件复制到%AppData%下的本地文件时遇到了同样的问题。对我来说效果很好的解决方案是删除目标文件,然后从源代码执行复制。

工作的代码:

file.replace(source,destination,backup,false) 

有效的代码:

File.Delete(destination)
File.Copy(source, destination)