Files
StandardSence/StandardScene.Core/CommonTools/AtomicFileUpdateHelper.cs
T
2026-06-14 11:19:15 +08:00

70 lines
2.8 KiB
C#

using System;
using System.Collections.Concurrent;
using System.IO;
using SimpleCore.Library;
namespace StandardScene.CommonTools
{
/// <summary>
/// 按文件路径串行化读写,保证高并发下对同一文件的更新原子、不覆盖其他记录。
/// 调用方在委托中完成“读当前内容 → 修改 → 返回新内容”,由本类负责加锁与写回。
/// </summary>
public static class AtomicFileUpdateHelper
{
private static readonly ConcurrentDictionary<string, object> PathLocks = new ConcurrentDictionary<string, object>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// 对指定路径执行原子更新:在持锁下读取当前文件内容,调用 update 得到新内容并写回。
/// 不修改其他记录时,应在 update 中仅变更需要变更的条目后返回完整内容。
/// </summary>
/// <param name="filePath">文件完整路径</param>
/// <param name="update">接收当前文件文本(若文件不存在则为 null),返回要写回的新文本;返回 null 表示不写入</param>
public static void ExecuteAtomicUpdate(string filePath, Func<string, string> update)
{
if (string.IsNullOrEmpty(filePath))
throw new ArgumentNullException(nameof(filePath));
if (update == null)
throw new ArgumentNullException(nameof(update));
var lockObj = PathLocks.GetOrAdd(filePath, _ => new object());
lock (lockObj)
{
string current = null;
try
{
if (File.Exists(filePath))
current = File.ReadAllText(filePath);
}
catch (Exception ex)
{
Diagnosis.Log($"AtomicFileUpdate read error: {filePath}, {ex.Message}", "AtomicFileUpdate", true);
throw;
}
string newContent = update(current);
if (newContent == null)
return;
var dir = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(dir))
Directory.CreateDirectory(dir);
try
{
var tempPath = filePath + ".tmp";
File.WriteAllText(tempPath, newContent);
if (File.Exists(filePath))
File.Replace(tempPath, filePath, null);
else
File.Move(tempPath, filePath);
}
catch (Exception ex)
{
Diagnosis.Log($"AtomicFileUpdate write error: {filePath}, {ex.Message}", "AtomicFileUpdate", true);
throw;
}
}
}
}
}