diff --git a/GFramework.Game/storage/FileStorage.cs b/GFramework.Game/storage/FileStorage.cs index cb92eeb..826280f 100644 --- a/GFramework.Game/storage/FileStorage.cs +++ b/GFramework.Game/storage/FileStorage.cs @@ -1,15 +1,19 @@ -using System.Text; +using System.Collections.Concurrent; +using System.Text; using GFramework.Core.Abstractions.storage; using GFramework.Game.Abstractions.serializer; namespace GFramework.Game.storage; /// -/// 基于文件系统的存储实现,实现了IStorage接口 +/// 基于文件系统的存储实现,实现了IStorage接口,支持按key细粒度锁保证线程安全 /// public sealed class FileStorage : IStorage { private readonly string _extension; + + // 每个key对应的锁对象 + private readonly ConcurrentDictionary _keyLocks = new(); private readonly string _rootPath; private readonly ISerializer _serializer; @@ -37,8 +41,13 @@ public sealed class FileStorage : IStorage public void Delete(string key) { var path = ToPath(key); - if (File.Exists(path)) - File.Delete(path); + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); + + lock (keyLock) + { + if (File.Exists(path)) + File.Delete(path); + } } #endregion @@ -53,7 +62,6 @@ public sealed class FileStorage : IStorage return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_')); } - #region Helpers /// @@ -102,15 +110,23 @@ public sealed class FileStorage : IStorage /// 检查指定键的存储项是否存在 /// /// 存储键 - /// 存在返回true,否则返回false + /// 如果存储项存在则返回true,否则返回false public bool Exists(string key) - => File.Exists(ToPath(key)); + { + var path = ToPath(key); + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); + + lock (keyLock) + { + return File.Exists(path); + } + } /// /// 异步检查指定键的存储项是否存在 /// /// 存储键 - /// 存在返回true,否则返回false + /// 如果存储项存在则返回true,否则返回false public Task ExistsAsync(string key) => Task.FromResult(Exists(key)); @@ -124,16 +140,20 @@ public sealed class FileStorage : IStorage /// 要反序列化的类型 /// 存储键 /// 反序列化后的对象 - /// 当指定键不存在时抛出 + /// 当存储键不存在时抛出 public T Read(string key) { var path = ToPath(key); + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); - if (!File.Exists(path)) - throw new FileNotFoundException($"Storage key not found: {key}", path); + lock (keyLock) + { + if (!File.Exists(path)) + throw new FileNotFoundException($"Storage key not found: {key}", path); - var content = File.ReadAllText(path, Encoding.UTF8); - return _serializer.Deserialize(content); + var content = File.ReadAllText(path, Encoding.UTF8); + return _serializer.Deserialize(content); + } } /// @@ -141,16 +161,21 @@ public sealed class FileStorage : IStorage /// /// 要反序列化的类型 /// 存储键 - /// 默认值 - /// 存在则返回反序列化后的对象,否则返回默认值 + /// 当存储键不存在时返回的默认值 + /// 反序列化后的对象或默认值 public T Read(string key, T defaultValue) { var path = ToPath(key); - if (!File.Exists(path)) - return defaultValue; + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); - var content = File.ReadAllText(path, Encoding.UTF8); - return _serializer.Deserialize(content); + lock (keyLock) + { + if (!File.Exists(path)) + return defaultValue; + + var content = File.ReadAllText(path, Encoding.UTF8); + return _serializer.Deserialize(content); + } } /// @@ -159,15 +184,27 @@ public sealed class FileStorage : IStorage /// 要反序列化的类型 /// 存储键 /// 反序列化后的对象 - /// 当指定键不存在时抛出 + /// 当存储键不存在时抛出 public async Task ReadAsync(string key) { var path = ToPath(key); + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); - if (!File.Exists(path)) - throw new FileNotFoundException($"Storage key not found: {key}", path); + // 异步操作依然使用lock保护文件读写 + lock (keyLock) + { + if (!File.Exists(path)) + throw new FileNotFoundException($"Storage key not found: {key}", path); + } + + // 读取文件内容可以使用异步IO,但要注意锁范围 + string content; + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var sr = new StreamReader(fs, Encoding.UTF8)) + { + content = await sr.ReadToEndAsync(); + } - var content = await File.ReadAllTextAsync(path, Encoding.UTF8); return _serializer.Deserialize(content); } @@ -178,29 +215,43 @@ public sealed class FileStorage : IStorage /// /// 写入指定键的存储项 /// - /// 要序列化的类型 + /// 要序列化的对象类型 /// 存储键 - /// 要存储的值 + /// 要存储的对象 public void Write(string key, T value) { var path = ToPath(key); + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); var content = _serializer.Serialize(value); - File.WriteAllText(path, content, Encoding.UTF8); + lock (keyLock) + { + File.WriteAllText(path, content, Encoding.UTF8); + } } /// /// 异步写入指定键的存储项 /// - /// 要序列化的类型 + /// 要序列化的对象类型 /// 存储键 - /// 要存储的值 + /// 要存储的对象 + /// 表示异步操作的任务 public async Task WriteAsync(string key, T value) { var path = ToPath(key); + var keyLock = _keyLocks.GetOrAdd(path, _ => new object()); var content = _serializer.Serialize(value); - await File.WriteAllTextAsync(path, content, Encoding.UTF8); + // 异步写也需要锁 + lock (keyLock) + { + using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + using var sw = new StreamWriter(fs, Encoding.UTF8); + sw.WriteAsync(content); + } + + await Task.CompletedTask; } #endregion