mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-24 20:34:29 +08:00
refactor(storage): 为FileStorage添加基于key的细粒度线程安全锁
- 引入ConcurrentDictionary存储每个key对应的锁对象 - 在Delete方法中添加key级别锁定确保删除操作的原子性 - 在Exists方法中使用key级别锁定保证存在性检查的线程安全 - 在Read同步方法中添加key级别锁定保护文件读取操作 - 在Write同步方法中添加key级别锁定保护文件写入操作 - 在ReadAsync异步读取方法中使用锁保护文件访问并优化异步IO - 在WriteAsync异步写入方法中使用锁保护文件访问并优化异步IO - 更新类注释说明支持细粒度锁的线程安全特性 - 改进XML文档注释的准确性和清晰度
This commit is contained in:
parent
5dc4feeff2
commit
4c997ffc07
@ -1,15 +1,19 @@
|
|||||||
using System.Text;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text;
|
||||||
using GFramework.Core.Abstractions.storage;
|
using GFramework.Core.Abstractions.storage;
|
||||||
using GFramework.Game.Abstractions.serializer;
|
using GFramework.Game.Abstractions.serializer;
|
||||||
|
|
||||||
namespace GFramework.Game.storage;
|
namespace GFramework.Game.storage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 基于文件系统的存储实现,实现了IStorage接口
|
/// 基于文件系统的存储实现,实现了IStorage接口,支持按key细粒度锁保证线程安全
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class FileStorage : IStorage
|
public sealed class FileStorage : IStorage
|
||||||
{
|
{
|
||||||
private readonly string _extension;
|
private readonly string _extension;
|
||||||
|
|
||||||
|
// 每个key对应的锁对象
|
||||||
|
private readonly ConcurrentDictionary<string, object> _keyLocks = new();
|
||||||
private readonly string _rootPath;
|
private readonly string _rootPath;
|
||||||
private readonly ISerializer _serializer;
|
private readonly ISerializer _serializer;
|
||||||
|
|
||||||
@ -37,8 +41,13 @@ public sealed class FileStorage : IStorage
|
|||||||
public void Delete(string key)
|
public void Delete(string key)
|
||||||
{
|
{
|
||||||
var path = ToPath(key);
|
var path = ToPath(key);
|
||||||
if (File.Exists(path))
|
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||||
File.Delete(path);
|
|
||||||
|
lock (keyLock)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -53,7 +62,6 @@ public sealed class FileStorage : IStorage
|
|||||||
return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_'));
|
return Path.GetInvalidFileNameChars().Aggregate(segment, (current, c) => current.Replace(c, '_'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -102,15 +110,23 @@ public sealed class FileStorage : IStorage
|
|||||||
/// 检查指定键的存储项是否存在
|
/// 检查指定键的存储项是否存在
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">存储键</param>
|
/// <param name="key">存储键</param>
|
||||||
/// <returns>存在返回true,否则返回false</returns>
|
/// <returns>如果存储项存在则返回true,否则返回false</returns>
|
||||||
public bool Exists(string key)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步检查指定键的存储项是否存在
|
/// 异步检查指定键的存储项是否存在
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">存储键</param>
|
/// <param name="key">存储键</param>
|
||||||
/// <returns>存在返回true,否则返回false</returns>
|
/// <returns>如果存储项存在则返回true,否则返回false</returns>
|
||||||
public Task<bool> ExistsAsync(string key)
|
public Task<bool> ExistsAsync(string key)
|
||||||
=> Task.FromResult(Exists(key));
|
=> Task.FromResult(Exists(key));
|
||||||
|
|
||||||
@ -124,16 +140,20 @@ public sealed class FileStorage : IStorage
|
|||||||
/// <typeparam name="T">要反序列化的类型</typeparam>
|
/// <typeparam name="T">要反序列化的类型</typeparam>
|
||||||
/// <param name="key">存储键</param>
|
/// <param name="key">存储键</param>
|
||||||
/// <returns>反序列化后的对象</returns>
|
/// <returns>反序列化后的对象</returns>
|
||||||
/// <exception cref="FileNotFoundException">当指定键不存在时抛出</exception>
|
/// <exception cref="FileNotFoundException">当存储键不存在时抛出</exception>
|
||||||
public T Read<T>(string key)
|
public T Read<T>(string key)
|
||||||
{
|
{
|
||||||
var path = ToPath(key);
|
var path = ToPath(key);
|
||||||
|
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||||
|
|
||||||
if (!File.Exists(path))
|
lock (keyLock)
|
||||||
throw new FileNotFoundException($"Storage key not found: {key}", path);
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
throw new FileNotFoundException($"Storage key not found: {key}", path);
|
||||||
|
|
||||||
var content = File.ReadAllText(path, Encoding.UTF8);
|
var content = File.ReadAllText(path, Encoding.UTF8);
|
||||||
return _serializer.Deserialize<T>(content);
|
return _serializer.Deserialize<T>(content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -141,16 +161,21 @@ public sealed class FileStorage : IStorage
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">要反序列化的类型</typeparam>
|
/// <typeparam name="T">要反序列化的类型</typeparam>
|
||||||
/// <param name="key">存储键</param>
|
/// <param name="key">存储键</param>
|
||||||
/// <param name="defaultValue">默认值</param>
|
/// <param name="defaultValue">当存储键不存在时返回的默认值</param>
|
||||||
/// <returns>存在则返回反序列化后的对象,否则返回默认值</returns>
|
/// <returns>反序列化后的对象或默认值</returns>
|
||||||
public T Read<T>(string key, T defaultValue)
|
public T Read<T>(string key, T defaultValue)
|
||||||
{
|
{
|
||||||
var path = ToPath(key);
|
var path = ToPath(key);
|
||||||
if (!File.Exists(path))
|
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||||
return defaultValue;
|
|
||||||
|
|
||||||
var content = File.ReadAllText(path, Encoding.UTF8);
|
lock (keyLock)
|
||||||
return _serializer.Deserialize<T>(content);
|
{
|
||||||
|
if (!File.Exists(path))
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
var content = File.ReadAllText(path, Encoding.UTF8);
|
||||||
|
return _serializer.Deserialize<T>(content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -159,15 +184,27 @@ public sealed class FileStorage : IStorage
|
|||||||
/// <typeparam name="T">要反序列化的类型</typeparam>
|
/// <typeparam name="T">要反序列化的类型</typeparam>
|
||||||
/// <param name="key">存储键</param>
|
/// <param name="key">存储键</param>
|
||||||
/// <returns>反序列化后的对象</returns>
|
/// <returns>反序列化后的对象</returns>
|
||||||
/// <exception cref="FileNotFoundException">当指定键不存在时抛出</exception>
|
/// <exception cref="FileNotFoundException">当存储键不存在时抛出</exception>
|
||||||
public async Task<T> ReadAsync<T>(string key)
|
public async Task<T> ReadAsync<T>(string key)
|
||||||
{
|
{
|
||||||
var path = ToPath(key);
|
var path = ToPath(key);
|
||||||
|
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||||
|
|
||||||
if (!File.Exists(path))
|
// 异步操作依然使用lock保护文件读写
|
||||||
throw new FileNotFoundException($"Storage key not found: {key}", path);
|
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<T>(content);
|
return _serializer.Deserialize<T>(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,29 +215,43 @@ public sealed class FileStorage : IStorage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 写入指定键的存储项
|
/// 写入指定键的存储项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">要序列化的类型</typeparam>
|
/// <typeparam name="T">要序列化的对象类型</typeparam>
|
||||||
/// <param name="key">存储键</param>
|
/// <param name="key">存储键</param>
|
||||||
/// <param name="value">要存储的值</param>
|
/// <param name="value">要存储的对象</param>
|
||||||
public void Write<T>(string key, T value)
|
public void Write<T>(string key, T value)
|
||||||
{
|
{
|
||||||
var path = ToPath(key);
|
var path = ToPath(key);
|
||||||
|
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||||
var content = _serializer.Serialize(value);
|
var content = _serializer.Serialize(value);
|
||||||
|
|
||||||
File.WriteAllText(path, content, Encoding.UTF8);
|
lock (keyLock)
|
||||||
|
{
|
||||||
|
File.WriteAllText(path, content, Encoding.UTF8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步写入指定键的存储项
|
/// 异步写入指定键的存储项
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">要序列化的类型</typeparam>
|
/// <typeparam name="T">要序列化的对象类型</typeparam>
|
||||||
/// <param name="key">存储键</param>
|
/// <param name="key">存储键</param>
|
||||||
/// <param name="value">要存储的值</param>
|
/// <param name="value">要存储的对象</param>
|
||||||
|
/// <returns>表示异步操作的任务</returns>
|
||||||
public async Task WriteAsync<T>(string key, T value)
|
public async Task WriteAsync<T>(string key, T value)
|
||||||
{
|
{
|
||||||
var path = ToPath(key);
|
var path = ToPath(key);
|
||||||
|
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||||
var content = _serializer.Serialize(value);
|
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
|
#endregion
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user