File.Replace 抛出 ERROR_UNABLE_TO_MOVE_REPLACMENT

问题描述 投票:0回答:4

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

代码是这样的:

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

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

c# io filesystems
4个回答
9
投票

好的,这样就找到问题了。看来传递到 ReplaceFile 的文件必须位于 SAME DRIVE

在这种情况下,用户已将其临时文件夹更改为 d: mp\

所以 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


0
投票

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

如果您是,请不要这样做,它们可能无法在区域设置为 1041(日语)的运行时运行。

而是使用框架来获取驱动部分并动态构建您的路径。

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

0
投票

我不建议您使用File.replace()。您可以删除目标文件,然后粘贴原始文件。

                    File.Delete(targetPath);
                    File.Copy(sourcePath, targetPath);

-1
投票

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

工作的代码:

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

有效的代码:

File.Delete(destination) File.Copy(source, destination)
    
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.