File.Replace throws ERROR_UNABLE_TO_MOVE_REPLACEMENT

本文关键字:TO MOVE REPLACEMENT UNABLE File throws ERROR Replace | 更新日期: 2023-09-27 18:20:43

我有一个"安全"的配置文件保存例程,当将用户配置数据写入磁盘时,该例程尝试原子化,避免磁盘缓存等。

代码如下:

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,所以我不认为这会导致问题。

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

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

注意:每次我的程序试图替换受影响机器上的文件时,都会引发此错误。。不仅仅是偶尔。

File.Replace throws ERROR_UNABLE_TO_MOVE_REPLACEMENT

好的,所以发现了问题。似乎传递到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:''etc?

如果你是,不要,它们可能在语言环境为1041(日语)的运行时不起作用。

相反,使用框架来获得驱动部分并动态构建您的路径。

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

我在OneDrive上处理源文件时遇到了同样的问题,将其复制到%AppData%下的本地文件。一个对我很有效的解决方案是删除目标文件,然后从源文件执行复制。

不起作用的代码:

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

有效的代码:

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