diff --git a/GFramework.Game/Storage/FileStorage.cs b/GFramework.Game/Storage/FileStorage.cs
index 2f08dce..e1140c6 100644
--- a/GFramework.Game/Storage/FileStorage.cs
+++ b/GFramework.Game/Storage/FileStorage.cs
@@ -1,8 +1,4 @@
-using System.IO;
-using System.Text;
-using GFramework.Core.Abstractions.Concurrency;
-using GFramework.Core.Abstractions.Serializer;
-using GFramework.Core.Concurrency;
+using GFramework.Core.Concurrency;
using GFramework.Game.Abstractions.Storage;
namespace GFramework.Game.Storage;
@@ -16,6 +12,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
private readonly int _bufferSize;
private readonly string _extension;
private readonly IAsyncKeyLockManager _lockManager;
+ private readonly bool _ownsLockManager;
private readonly string _rootPath;
private readonly ISerializer _serializer;
private bool _disposed;
@@ -35,7 +32,17 @@ public sealed class FileStorage : IFileStorage, IDisposable
_serializer = serializer;
_extension = extension;
_bufferSize = bufferSize;
- _lockManager = lockManager ?? new AsyncKeyLockManager();
+
+ if (lockManager == null)
+ {
+ _lockManager = new AsyncKeyLockManager();
+ _ownsLockManager = true;
+ }
+ else
+ {
+ _lockManager = lockManager;
+ _ownsLockManager = false;
+ }
Directory.CreateDirectory(_rootPath);
}
@@ -47,7 +54,12 @@ public sealed class FileStorage : IFileStorage, IDisposable
{
if (_disposed) return;
_disposed = true;
- _lockManager.Dispose();
+
+ // 只释放内部创建的锁管理器
+ if (_ownsLockManager)
+ {
+ _lockManager.Dispose();
+ }
}
///
@@ -108,9 +120,13 @@ public sealed class FileStorage : IFileStorage, IDisposable
/// 删除指定键的存储项
///
/// 存储键,用于标识要删除的存储项
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public void Delete(string key)
{
- DeleteAsync(key).GetAwaiter().GetResult();
+ DeleteAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -123,7 +139,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this);
var path = ToPath(key);
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
if (File.Exists(path))
File.Delete(path);
@@ -139,9 +155,13 @@ public sealed class FileStorage : IFileStorage, IDisposable
///
/// 存储键
/// 如果存储项存在则返回true,否则返回false
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public bool Exists(string key)
{
- return ExistsAsync(key).GetAwaiter().GetResult();
+ return ExistsAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -154,7 +174,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this);
var path = ToPath(key);
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
return File.Exists(path);
}
@@ -171,9 +191,13 @@ public sealed class FileStorage : IFileStorage, IDisposable
/// 存储键
/// 反序列化后的对象
/// 当存储键不存在时抛出
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public T Read(string key)
{
- return ReadAsync(key).GetAwaiter().GetResult();
+ return ReadAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -208,7 +232,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this);
var path = ToPath(key);
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
if (!File.Exists(path))
throw new FileNotFoundException($"Storage key not found: {key}", path);
@@ -222,7 +246,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
useAsync: true);
using var sr = new StreamReader(fs, Encoding.UTF8);
- var content = await sr.ReadToEndAsync();
+ var content = await sr.ReadToEndAsync().ConfigureAwait(false);
return _serializer.Deserialize(content);
}
}
@@ -304,9 +328,13 @@ public sealed class FileStorage : IFileStorage, IDisposable
/// 要序列化的对象类型
/// 存储键
/// 要存储的对象
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public void Write(string key, T value)
{
- WriteAsync(key, value).GetAwaiter().GetResult();
+ WriteAsync(key, value).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -322,7 +350,7 @@ public sealed class FileStorage : IFileStorage, IDisposable
var path = ToPath(key);
var tempPath = path + ".tmp";
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
try
{
@@ -338,8 +366,8 @@ public sealed class FileStorage : IFileStorage, IDisposable
useAsync: true))
{
await using var sw = new StreamWriter(fs, Encoding.UTF8);
- await sw.WriteAsync(content);
- await sw.FlushAsync();
+ await sw.WriteAsync(content).ConfigureAwait(false);
+ await sw.FlushAsync().ConfigureAwait(false);
}
// 原子性替换目标文件
diff --git a/GFramework.Godot/Storage/GodotFileStorage.cs b/GFramework.Godot/Storage/GodotFileStorage.cs
index 3fc22e3..ca46db7 100644
--- a/GFramework.Godot/Storage/GodotFileStorage.cs
+++ b/GFramework.Godot/Storage/GodotFileStorage.cs
@@ -1,13 +1,6 @@
-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;
@@ -18,6 +11,7 @@ namespace GFramework.Godot.Storage;
public sealed class GodotFileStorage : IStorage, IDisposable
{
private readonly IAsyncKeyLockManager _lockManager;
+ private readonly bool _ownsLockManager;
private readonly ISerializer _serializer;
private bool _disposed;
@@ -29,7 +23,17 @@ public sealed class GodotFileStorage : IStorage, IDisposable
public GodotFileStorage(ISerializer serializer, IAsyncKeyLockManager? lockManager = null)
{
_serializer = serializer ?? throw new ArgumentNullException(nameof(serializer));
- _lockManager = lockManager ?? new AsyncKeyLockManager();
+
+ if (lockManager == null)
+ {
+ _lockManager = new AsyncKeyLockManager();
+ _ownsLockManager = true;
+ }
+ else
+ {
+ _lockManager = lockManager;
+ _ownsLockManager = false;
+ }
}
///
@@ -39,7 +43,12 @@ public sealed class GodotFileStorage : IStorage, IDisposable
{
if (_disposed) return;
_disposed = true;
- _lockManager.Dispose();
+
+ // 只释放内部创建的锁管理器
+ if (_ownsLockManager)
+ {
+ _lockManager.Dispose();
+ }
}
#region Delete
@@ -48,9 +57,13 @@ public sealed class GodotFileStorage : IStorage, IDisposable
/// 删除指定键对应的文件
///
/// 存储键
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public void Delete(string key)
{
- DeleteAsync(key).GetAwaiter().GetResult();
+ DeleteAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -63,7 +76,7 @@ public sealed class GodotFileStorage : IStorage, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this);
var path = ToAbsolutePath(key);
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
// 处理Godot文件系统路径的删除操作
if (path.IsGodotPath())
@@ -142,9 +155,13 @@ public sealed class GodotFileStorage : IStorage, IDisposable
///
/// 存储键
/// 文件存在返回 true,否则返回 false
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public bool Exists(string key)
{
- return ExistsAsync(key).GetAwaiter().GetResult();
+ return ExistsAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -157,7 +174,7 @@ public sealed class GodotFileStorage : IStorage, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this);
var path = ToAbsolutePath(key);
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
if (!path.IsGodotPath()) return File.Exists(path);
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
@@ -176,9 +193,13 @@ public sealed class GodotFileStorage : IStorage, IDisposable
/// 存储键
/// 反序列化后的对象实例
/// 当指定键对应的文件不存在时抛出
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public T Read(string key)
{
- return ReadAsync(key).GetAwaiter().GetResult();
+ return ReadAsync(key).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -212,7 +233,7 @@ public sealed class GodotFileStorage : IStorage, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this);
var path = ToAbsolutePath(key);
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
string content;
@@ -226,7 +247,7 @@ public sealed class GodotFileStorage : IStorage, IDisposable
{
if (!File.Exists(path))
throw new FileNotFoundException($"Storage key not found: {key}", path);
- content = await File.ReadAllTextAsync(path, Encoding.UTF8);
+ content = await File.ReadAllTextAsync(path, Encoding.UTF8).ConfigureAwait(false);
}
return _serializer.Deserialize(content);
@@ -331,9 +352,13 @@ public sealed class GodotFileStorage : IStorage, IDisposable
/// 要序列化的对象类型
/// 存储键
/// 要写入的对象实例
+ ///
+ /// 此方法通过同步等待异步操作完成,可能在具有同步上下文的环境(例如 UI 线程、经典 ASP.NET)中导致死锁。
+ /// 仅在无法使用异步 API 时使用。如果可能,请优先使用 。
+ ///
public void Write(string key, T value)
{
- WriteAsync(key, value).GetAwaiter().GetResult();
+ WriteAsync(key, value).ConfigureAwait(false).GetAwaiter().GetResult();
}
///
@@ -348,7 +373,7 @@ public sealed class GodotFileStorage : IStorage, IDisposable
ObjectDisposedException.ThrowIf(_disposed, this);
var path = ToAbsolutePath(key);
- await using (await _lockManager.AcquireLockAsync(path))
+ await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false))
{
var content = _serializer.Serialize(value);
if (path.IsGodotPath())
@@ -360,7 +385,7 @@ public sealed class GodotFileStorage : IStorage, IDisposable
else
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
- await File.WriteAllTextAsync(path, content, Encoding.UTF8);
+ await File.WriteAllTextAsync(path, content, Encoding.UTF8).ConfigureAwait(false);
}
}
}