mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
Merge pull request #98 from GeWuYou/refactor/storage-async-safety
refactor(storage): 重构文件存储实现以支持异步安全锁和原子写入
This commit is contained in:
commit
d038b67e29
@ -1,21 +1,25 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using GFramework.Core.Abstractions.Concurrency;
|
||||
using GFramework.Core.Abstractions.Serializer;
|
||||
using GFramework.Core.Concurrency;
|
||||
using GFramework.Game.Abstractions.Storage;
|
||||
|
||||
namespace GFramework.Game.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// 基于文件系统的存储实现,实现了IFileStorage接口,支持按key细粒度锁保证线程安全
|
||||
/// 使用异步安全的锁机制、原子写入和自动清理
|
||||
/// </summary>
|
||||
public sealed class FileStorage : IFileStorage
|
||||
public sealed class FileStorage : IFileStorage, IDisposable
|
||||
{
|
||||
private readonly int _bufferSize;
|
||||
private readonly string _extension;
|
||||
|
||||
// 每个key对应的锁对象
|
||||
private readonly ConcurrentDictionary<string, object> _keyLocks = new();
|
||||
private readonly IAsyncKeyLockManager _lockManager;
|
||||
private readonly bool _ownsLockManager;
|
||||
private readonly string _rootPath;
|
||||
private readonly ISerializer _serializer;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化FileStorage实例
|
||||
@ -23,15 +27,45 @@ public sealed class FileStorage : IFileStorage
|
||||
/// <param name="rootPath">存储根目录路径</param>
|
||||
/// <param name="serializer">序列化器实例</param>
|
||||
/// <param name="extension">存储文件的扩展名</param>
|
||||
public FileStorage(string rootPath, ISerializer serializer, string extension = ".dat")
|
||||
/// <param name="bufferSize">IO 缓冲区大小,默认 8KB</param>
|
||||
/// <param name="lockManager">可选的锁管理器,用于依赖注入</param>
|
||||
public FileStorage(string rootPath, ISerializer serializer, string extension = ".dat", int bufferSize = 8192,
|
||||
IAsyncKeyLockManager? lockManager = null)
|
||||
{
|
||||
_rootPath = rootPath;
|
||||
_serializer = serializer;
|
||||
_extension = extension;
|
||||
_bufferSize = bufferSize;
|
||||
|
||||
if (lockManager == null)
|
||||
{
|
||||
_lockManager = new AsyncKeyLockManager();
|
||||
_ownsLockManager = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lockManager = lockManager;
|
||||
_ownsLockManager = false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(_rootPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
// 只释放内部创建的锁管理器
|
||||
if (_ownsLockManager)
|
||||
{
|
||||
_lockManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理文件段字符串,将其中的无效文件名字符替换为下划线
|
||||
/// </summary>
|
||||
@ -90,21 +124,13 @@ public sealed class FileStorage : IFileStorage
|
||||
/// 删除指定键的存储项
|
||||
/// </summary>
|
||||
/// <param name="key">存储键,用于标识要删除的存储项</param>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="DeleteAsync"/>。
|
||||
/// </remarks>
|
||||
public void Delete(string key)
|
||||
{
|
||||
// 将键转换为文件路径
|
||||
var path = ToPath(key);
|
||||
|
||||
// 获取或创建与路径关联的锁对象,确保线程安全
|
||||
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||
|
||||
// 使用锁确保同一时间只有一个线程操作该路径的文件
|
||||
lock (keyLock)
|
||||
{
|
||||
// 如果文件存在,则删除该文件
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
DeleteAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -112,10 +138,16 @@ public sealed class FileStorage : IFileStorage
|
||||
/// </summary>
|
||||
/// <param name="key">存储键,用于标识要删除的存储项</param>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public Task DeleteAsync(string key)
|
||||
public async Task DeleteAsync(string key)
|
||||
{
|
||||
// 在线程池中运行同步删除方法以实现异步操作
|
||||
return Task.Run(() => Delete(key));
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToPath(key);
|
||||
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -127,15 +159,13 @@ public sealed class FileStorage : IFileStorage
|
||||
/// </summary>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>如果存储项存在则返回true,否则返回false</returns>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="ExistsAsync"/>。
|
||||
/// </remarks>
|
||||
public bool Exists(string key)
|
||||
{
|
||||
var path = ToPath(key);
|
||||
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||
|
||||
lock (keyLock)
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
return ExistsAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -143,9 +173,15 @@ public sealed class FileStorage : IFileStorage
|
||||
/// </summary>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>如果存储项存在则返回true,否则返回false</returns>
|
||||
public Task<bool> ExistsAsync(string key)
|
||||
public async Task<bool> ExistsAsync(string key)
|
||||
{
|
||||
return Task.FromResult(Exists(key));
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToPath(key);
|
||||
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -159,19 +195,13 @@ public sealed class FileStorage : IFileStorage
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>反序列化后的对象</returns>
|
||||
/// <exception cref="FileNotFoundException">当存储键不存在时抛出</exception>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="ReadAsync{T}(string)"/>。
|
||||
/// </remarks>
|
||||
public T Read<T>(string key)
|
||||
{
|
||||
var path = ToPath(key);
|
||||
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||
|
||||
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<T>(content);
|
||||
}
|
||||
return ReadAsync<T>(key).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -183,16 +213,14 @@ public sealed class FileStorage : IFileStorage
|
||||
/// <returns>反序列化后的对象或默认值</returns>
|
||||
public T Read<T>(string key, T defaultValue)
|
||||
{
|
||||
var path = ToPath(key);
|
||||
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||
|
||||
lock (keyLock)
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
try
|
||||
{
|
||||
return Read<T>(key);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
return defaultValue;
|
||||
|
||||
var content = File.ReadAllText(path, Encoding.UTF8);
|
||||
return _serializer.Deserialize<T>(content);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,26 +233,27 @@ public sealed class FileStorage : IFileStorage
|
||||
/// <exception cref="FileNotFoundException">当存储键不存在时抛出</exception>
|
||||
public async Task<T> ReadAsync<T>(string key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToPath(key);
|
||||
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||
|
||||
// 异步操作依然使用lock保护文件读写
|
||||
lock (keyLock)
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
throw new FileNotFoundException($"Storage key not found: {key}", path);
|
||||
}
|
||||
|
||||
// 读取文件内容可以使用异步IO,但要注意锁范围
|
||||
string content;
|
||||
await using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
using (var sr = new StreamReader(fs, Encoding.UTF8))
|
||||
{
|
||||
content = await sr.ReadToEndAsync();
|
||||
}
|
||||
await using var fs = new FileStream(
|
||||
path,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read,
|
||||
_bufferSize,
|
||||
useAsync: true);
|
||||
|
||||
using var sr = new StreamReader(fs, Encoding.UTF8);
|
||||
var content = await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
return _serializer.Deserialize<T>(content);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -303,20 +332,17 @@ public sealed class FileStorage : IFileStorage
|
||||
/// <typeparam name="T">要序列化的对象类型</typeparam>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <param name="value">要存储的对象</param>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="WriteAsync{T}"/>。
|
||||
/// </remarks>
|
||||
public void Write<T>(string key, T value)
|
||||
{
|
||||
var path = ToPath(key);
|
||||
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||
var content = _serializer.Serialize(value);
|
||||
|
||||
lock (keyLock)
|
||||
{
|
||||
File.WriteAllText(path, content, Encoding.UTF8);
|
||||
}
|
||||
WriteAsync(key, value).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步写入指定键的存储项
|
||||
/// 异步写入指定键的存储项,使用原子写入防止文件损坏
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要序列化的对象类型</typeparam>
|
||||
/// <param name="key">存储键</param>
|
||||
@ -324,19 +350,41 @@ public sealed class FileStorage : IFileStorage
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public async Task WriteAsync<T>(string key, T value)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToPath(key);
|
||||
var keyLock = _keyLocks.GetOrAdd(path, _ => new object());
|
||||
var tempPath = path + ".tmp";
|
||||
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = _serializer.Serialize(value);
|
||||
|
||||
// 异步写也需要锁
|
||||
lock (keyLock)
|
||||
// 先写入临时文件
|
||||
await using (var fs = new FileStream(
|
||||
tempPath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.None,
|
||||
_bufferSize,
|
||||
useAsync: true))
|
||||
{
|
||||
using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
using var sw = new StreamWriter(fs, Encoding.UTF8);
|
||||
sw.WriteAsync(content);
|
||||
await using var sw = new StreamWriter(fs, Encoding.UTF8);
|
||||
await sw.WriteAsync(content).ConfigureAwait(false);
|
||||
await sw.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
// 原子性替换目标文件
|
||||
File.Move(tempPath, path, overwrite: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 清理临时文件
|
||||
if (File.Exists(tempPath))
|
||||
File.Delete(tempPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -1,35 +1,60 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using GFramework.Core.Abstractions.Concurrency;
|
||||
using GFramework.Core.Abstractions.Serializer;
|
||||
using GFramework.Core.Abstractions.Storage;
|
||||
using GFramework.Core.Concurrency;
|
||||
using GFramework.Godot.Extensions;
|
||||
using Godot;
|
||||
using Error = Godot.Error;
|
||||
using FileAccess = Godot.FileAccess;
|
||||
|
||||
namespace GFramework.Godot.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Godot 特化的文件存储实现,支持 res://、user:// 和普通文件路径
|
||||
/// 支持按 key 细粒度锁保证线程安全
|
||||
/// 支持按 key 细粒度锁保证线程安全,使用异步安全的锁机制
|
||||
/// </summary>
|
||||
public sealed class GodotFileStorage : IStorage
|
||||
public sealed class GodotFileStorage : IStorage, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 每个 key 对应的锁对象
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, object> _keyLocks = new();
|
||||
|
||||
private readonly IAsyncKeyLockManager _lockManager;
|
||||
private readonly bool _ownsLockManager;
|
||||
private readonly ISerializer _serializer;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化 Godot 文件存储
|
||||
/// </summary>
|
||||
/// <param name="serializer">序列化器实例</param>
|
||||
public GodotFileStorage(ISerializer serializer)
|
||||
/// <param name="lockManager">可选的锁管理器,用于依赖注入</param>
|
||||
public GodotFileStorage(ISerializer serializer, IAsyncKeyLockManager? lockManager = null)
|
||||
{
|
||||
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
|
||||
|
||||
if (lockManager == null)
|
||||
{
|
||||
_lockManager = new AsyncKeyLockManager();
|
||||
_ownsLockManager = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lockManager = lockManager;
|
||||
_ownsLockManager = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
// 只释放内部创建的锁管理器
|
||||
if (_ownsLockManager)
|
||||
{
|
||||
_lockManager.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#region Delete
|
||||
@ -38,12 +63,26 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// 删除指定键对应的文件
|
||||
/// </summary>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="DeleteAsync"/>。
|
||||
/// </remarks>
|
||||
public void Delete(string key)
|
||||
{
|
||||
var path = ToAbsolutePath(key);
|
||||
var keyLock = GetLock(path);
|
||||
DeleteAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
lock (keyLock)
|
||||
/// <summary>
|
||||
/// 异步删除指定键对应的文件
|
||||
/// </summary>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>异步任务</returns>
|
||||
public async Task DeleteAsync(string key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToAbsolutePath(key);
|
||||
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
// 处理Godot文件系统路径的删除操作
|
||||
if (path.IsGodotPath())
|
||||
@ -61,19 +100,6 @@ public sealed class GodotFileStorage : IStorage
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除完成后尝试移除锁,防止锁字典无限增长
|
||||
_keyLocks.TryRemove(path, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步删除指定键对应的文件
|
||||
/// </summary>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>异步任务</returns>
|
||||
public async Task DeleteAsync(string key)
|
||||
{
|
||||
await Task.Run(() => Delete(key));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -126,16 +152,6 @@ public sealed class GodotFileStorage : IStorage
|
||||
return Path.Combine(dir, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定路径对应的锁对象,如果不存在则创建新的锁对象
|
||||
/// </summary>
|
||||
/// <param name="path">文件路径</param>
|
||||
/// <returns>对应路径的锁对象</returns>
|
||||
private object GetLock(string path)
|
||||
{
|
||||
return _keyLocks.GetOrAdd(path, _ => new object());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Exists
|
||||
@ -145,17 +161,13 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// </summary>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>文件存在返回 true,否则返回 false</returns>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="ExistsAsync"/>。
|
||||
/// </remarks>
|
||||
public bool Exists(string key)
|
||||
{
|
||||
var path = ToAbsolutePath(key);
|
||||
var keyLock = GetLock(path);
|
||||
|
||||
lock (keyLock)
|
||||
{
|
||||
if (!path.IsGodotPath()) return File.Exists(path);
|
||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
||||
return file != null;
|
||||
}
|
||||
return ExistsAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -163,9 +175,17 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// </summary>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>表示异步操作的任务,结果为布尔值表示文件是否存在</returns>
|
||||
public Task<bool> ExistsAsync(string key)
|
||||
public async Task<bool> ExistsAsync(string key)
|
||||
{
|
||||
return Task.FromResult(Exists(key));
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToAbsolutePath(key);
|
||||
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
if (!path.IsGodotPath()) return File.Exists(path);
|
||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
||||
return file != null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -179,30 +199,13 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// <param name="key">存储键</param>
|
||||
/// <returns>反序列化后的对象实例</returns>
|
||||
/// <exception cref="FileNotFoundException">当指定键对应的文件不存在时抛出</exception>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="ReadAsync{T}(string)"/>。
|
||||
/// </remarks>
|
||||
public T Read<T>(string key)
|
||||
{
|
||||
var path = ToAbsolutePath(key);
|
||||
var keyLock = GetLock(path);
|
||||
|
||||
lock (keyLock)
|
||||
{
|
||||
string content;
|
||||
|
||||
if (path.IsGodotPath())
|
||||
{
|
||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
||||
if (file == null) throw new FileNotFoundException($"Storage key not found: {key}", path);
|
||||
content = file.GetAsText();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
throw new FileNotFoundException($"Storage key not found: {key}", path);
|
||||
content = File.ReadAllText(path, Encoding.UTF8);
|
||||
}
|
||||
|
||||
return _serializer.Deserialize<T>(content);
|
||||
}
|
||||
return ReadAsync<T>(key).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -214,16 +217,15 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// <returns>反序列化后的对象实例或默认值</returns>
|
||||
public T Read<T>(string key, T defaultValue)
|
||||
{
|
||||
var path = ToAbsolutePath(key);
|
||||
var keyLock = GetLock(path);
|
||||
|
||||
lock (keyLock)
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
try
|
||||
{
|
||||
if ((path.IsGodotPath() && !FileAccess.FileExists(path)) || (!path.IsGodotPath() && !File.Exists(path)))
|
||||
return defaultValue;
|
||||
|
||||
return Read<T>(key);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -234,12 +236,10 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// <returns>表示异步操作的任务,结果为反序列化后的对象实例</returns>
|
||||
public async Task<T> ReadAsync<T>(string key)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToAbsolutePath(key);
|
||||
var keyLock = GetLock(path);
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
lock (keyLock)
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
string content;
|
||||
|
||||
@ -253,12 +253,11 @@ public sealed class GodotFileStorage : IStorage
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
throw new FileNotFoundException($"Storage key not found: {key}", path);
|
||||
content = File.ReadAllText(path, Encoding.UTF8);
|
||||
content = await File.ReadAllTextAsync(path, Encoding.UTF8).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return _serializer.Deserialize<T>(content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -359,26 +358,13 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// <typeparam name="T">要序列化的对象类型</typeparam>
|
||||
/// <param name="key">存储键</param>
|
||||
/// <param name="value">要写入的对象实例</param>
|
||||
/// <remarks>
|
||||
/// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
|
||||
/// 仅在无法使用异步 API 时使用。如果可能,请优先使用 <see cref="WriteAsync{T}"/>。
|
||||
/// </remarks>
|
||||
public void Write<T>(string key, T value)
|
||||
{
|
||||
var path = ToAbsolutePath(key);
|
||||
var keyLock = GetLock(path);
|
||||
|
||||
lock (keyLock)
|
||||
{
|
||||
var content = _serializer.Serialize(value);
|
||||
if (path.IsGodotPath())
|
||||
{
|
||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
||||
if (file == null) throw new IOException($"Cannot write file: {path}");
|
||||
file.StoreString(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
File.WriteAllText(path, content, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
WriteAsync(key, value).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -390,7 +376,24 @@ public sealed class GodotFileStorage : IStorage
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public async Task WriteAsync<T>(string key, T value)
|
||||
{
|
||||
await Task.Run(() => Write(key, value));
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
var path = ToAbsolutePath(key);
|
||||
|
||||
await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
|
||||
{
|
||||
var content = _serializer.Serialize(value);
|
||||
if (path.IsGodotPath())
|
||||
{
|
||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
||||
if (file == null) throw new IOException($"Cannot write file: {path}");
|
||||
file.StoreString(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
||||
await File.WriteAllTextAsync(path, content, Encoding.UTF8).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user