From 4c997ffc07083b1ca7791adbf6640ba6a9a4e73f Mon Sep 17 00:00:00 2001
From: GwWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Sun, 11 Jan 2026 20:44:26 +0800
Subject: [PATCH] =?UTF-8?q?refactor(storage):=20=E4=B8=BAFileStorage?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9F=BA=E4=BA=8Ekey=E7=9A=84=E7=BB=86?=
=?UTF-8?q?=E7=B2=92=E5=BA=A6=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8=E9=94=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 引入ConcurrentDictionary存储每个key对应的锁对象
- 在Delete方法中添加key级别锁定确保删除操作的原子性
- 在Exists方法中使用key级别锁定保证存在性检查的线程安全
- 在Read同步方法中添加key级别锁定保护文件读取操作
- 在Write同步方法中添加key级别锁定保护文件写入操作
- 在ReadAsync异步读取方法中使用锁保护文件访问并优化异步IO
- 在WriteAsync异步写入方法中使用锁保护文件访问并优化异步IO
- 更新类注释说明支持细粒度锁的线程安全特性
- 改进XML文档注释的准确性和清晰度
---
GFramework.Game/storage/FileStorage.cs | 109 ++++++++++++++++++-------
1 file changed, 80 insertions(+), 29 deletions(-)
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