fix(game): 修复数据仓库与场景路由分析器警告

- 修复数据仓库异步存储调用的 ConfigureAwait(false) 使用,消除目标 MA0004 警告

- 更新 UnifiedSettingsDataRepository 的字符串键字典 comparer 为 StringComparer.Ordinal,消除目标 MA0002 警告

- 保留场景切换流程在当前上下文继续执行,并显式使用 ConfigureAwait(true) 说明上下文约束
This commit is contained in:
gewuyou 2026-04-27 07:41:10 +08:00
parent 7e13752bb1
commit e3eec5452c
4 changed files with 61 additions and 57 deletions

View File

@ -52,7 +52,9 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options =
var key = location.ToStorageKey(); var key = location.ToStorageKey();
// 检查存储中是否存在指定键的数据 // 检查存储中是否存在指定键的数据
T result = await Storage.ExistsAsync(key) ? await Storage.ReadAsync<T>(key) : new T(); T result = await Storage.ExistsAsync(key).ConfigureAwait(false)
? await Storage.ReadAsync<T>(key).ConfigureAwait(false)
: new T();
// 如果启用事件功能,则发送数据加载完成事件 // 如果启用事件功能,则发送数据加载完成事件
if (_options.EnableEvents) if (_options.EnableEvents)
@ -70,7 +72,7 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options =
public async Task SaveAsync<T>(IDataLocation location, T data) public async Task SaveAsync<T>(IDataLocation location, T data)
where T : class, IData where T : class, IData
{ {
await SaveCoreAsync(location, data, emitSavedEvent: true); await SaveCoreAsync(location, data, emitSavedEvent: true).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -91,12 +93,12 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options =
{ {
var key = location.ToStorageKey(); var key = location.ToStorageKey();
if (!await Storage.ExistsAsync(key)) if (!await Storage.ExistsAsync(key).ConfigureAwait(false))
{ {
return; return;
} }
await Storage.DeleteAsync(key); await Storage.DeleteAsync(key).ConfigureAwait(false);
if (_options.EnableEvents) if (_options.EnableEvents)
this.SendEvent(new DataDeletedEvent(location)); this.SendEvent(new DataDeletedEvent(location));
} }
@ -113,7 +115,7 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options =
// 但抑制逐项 DataSavedEvent避免监听器对同一批次收到重复语义的事件。 // 但抑制逐项 DataSavedEvent避免监听器对同一批次收到重复语义的事件。
foreach (var (location, data) in valueTuples) foreach (var (location, data) in valueTuples)
{ {
await SaveCoreUntypedAsync(location, data, emitSavedEvent: false); await SaveCoreUntypedAsync(location, data, emitSavedEvent: false).ConfigureAwait(false);
} }
if (_options.EnableEvents) if (_options.EnableEvents)
@ -140,8 +142,8 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options =
{ {
var key = location.ToStorageKey(); var key = location.ToStorageKey();
await BackupIfNeededAsync<T>(key); await BackupIfNeededAsync<T>(key).ConfigureAwait(false);
await Storage.WriteAsync(key, data); await Storage.WriteAsync(key, data).ConfigureAwait(false);
if (emitSavedEvent && _options.EnableEvents) if (emitSavedEvent && _options.EnableEvents)
{ {
@ -156,14 +158,14 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options =
private async Task BackupIfNeededAsync<T>(string key) private async Task BackupIfNeededAsync<T>(string key)
where T : class, IData where T : class, IData
{ {
if (!_options.AutoBackup || !await Storage.ExistsAsync(key)) if (!_options.AutoBackup || !await Storage.ExistsAsync(key).ConfigureAwait(false))
{ {
return; return;
} }
var backupKey = $"{key}.backup"; var backupKey = $"{key}.backup";
var existing = await Storage.ReadAsync<T>(key); var existing = await Storage.ReadAsync<T>(key).ConfigureAwait(false);
await Storage.WriteAsync(backupKey, existing); await Storage.WriteAsync(backupKey, existing).ConfigureAwait(false);
} }
/// <summary> /// <summary>

View File

@ -99,7 +99,7 @@ public class SaveRepository<TSaveData> : AbstractContextUtility, ISaveRepository
public async Task<bool> ExistsAsync(int slot) public async Task<bool> ExistsAsync(int slot)
{ {
var storage = GetSlotStorage(slot); var storage = GetSlotStorage(slot);
return await storage.ExistsAsync(_config.SaveFileName); return await storage.ExistsAsync(_config.SaveFileName).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -111,10 +111,10 @@ public class SaveRepository<TSaveData> : AbstractContextUtility, ISaveRepository
{ {
var storage = GetSlotStorage(slot); var storage = GetSlotStorage(slot);
if (await storage.ExistsAsync(_config.SaveFileName)) if (await storage.ExistsAsync(_config.SaveFileName).ConfigureAwait(false))
{ {
var loaded = await storage.ReadAsync<TSaveData>(_config.SaveFileName); var loaded = await storage.ReadAsync<TSaveData>(_config.SaveFileName).ConfigureAwait(false);
return await MigrateIfNeededAsync(slot, storage, loaded); return await MigrateIfNeededAsync(slot, storage, loaded).ConfigureAwait(false);
} }
return new TSaveData(); return new TSaveData();
@ -130,11 +130,11 @@ public class SaveRepository<TSaveData> : AbstractContextUtility, ISaveRepository
var slotPath = $"{_config.SaveSlotPrefix}{slot}"; var slotPath = $"{_config.SaveSlotPrefix}{slot}";
// 确保槽位目录存在 // 确保槽位目录存在
if (!await _rootStorage.DirectoryExistsAsync(slotPath)) if (!await _rootStorage.DirectoryExistsAsync(slotPath).ConfigureAwait(false))
await _rootStorage.CreateDirectoryAsync(slotPath); await _rootStorage.CreateDirectoryAsync(slotPath).ConfigureAwait(false);
var storage = GetSlotStorage(slot); var storage = GetSlotStorage(slot);
await storage.WriteAsync(_config.SaveFileName, data); await storage.WriteAsync(_config.SaveFileName, data).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -144,7 +144,7 @@ public class SaveRepository<TSaveData> : AbstractContextUtility, ISaveRepository
public async Task DeleteAsync(int slot) public async Task DeleteAsync(int slot)
{ {
var storage = GetSlotStorage(slot); var storage = GetSlotStorage(slot);
await storage.DeleteAsync(_config.SaveFileName); await storage.DeleteAsync(_config.SaveFileName).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -154,7 +154,7 @@ public class SaveRepository<TSaveData> : AbstractContextUtility, ISaveRepository
public async Task<IReadOnlyList<int>> ListSlotsAsync() public async Task<IReadOnlyList<int>> ListSlotsAsync()
{ {
// 列举所有槽位目录 // 列举所有槽位目录
var directories = await _rootStorage.ListDirectoriesAsync(); var directories = await _rootStorage.ListDirectoriesAsync().ConfigureAwait(false);
var slots = new List<int>(); var slots = new List<int>();
@ -171,7 +171,7 @@ public class SaveRepository<TSaveData> : AbstractContextUtility, ISaveRepository
// 直接检查存档文件是否存在,避免重复创建 ScopedStorage // 直接检查存档文件是否存在,避免重复创建 ScopedStorage
var saveFilePath = $"{dirName}/{_config.SaveFileName}"; var saveFilePath = $"{dirName}/{_config.SaveFileName}";
if (await _rootStorage.ExistsAsync(saveFilePath)) if (await _rootStorage.ExistsAsync(saveFilePath).ConfigureAwait(false))
slots.Add(slot); slots.Add(slot);
} }
@ -246,7 +246,7 @@ public class SaveRepository<TSaveData> : AbstractContextUtility, ISaveRepository
$"{typeof(TSaveData).Name} in slot {slot}", $"{typeof(TSaveData).Name} in slot {slot}",
"save migration"); "save migration");
await storage.WriteAsync(_config.SaveFileName, migrated); await storage.WriteAsync(_config.SaveFileName, migrated).ConfigureAwait(false);
return migrated; return migrated;
} }

View File

@ -37,7 +37,7 @@ public class UnifiedSettingsDataRepository(
{ {
private readonly SemaphoreSlim _lock = new(1, 1); private readonly SemaphoreSlim _lock = new(1, 1);
private readonly DataRepositoryOptions _options = options ?? new DataRepositoryOptions(); private readonly DataRepositoryOptions _options = options ?? new DataRepositoryOptions();
private readonly Dictionary<string, Type> _typeRegistry = new(); private readonly Dictionary<string, Type> _typeRegistry = new(StringComparer.Ordinal);
private UnifiedSettingsFile? _file; private UnifiedSettingsFile? _file;
private bool _loaded; private bool _loaded;
private IRuntimeTypeSerializer? _serializer = serializer; private IRuntimeTypeSerializer? _serializer = serializer;
@ -67,7 +67,7 @@ public class UnifiedSettingsDataRepository(
public async Task<T> LoadAsync<T>(IDataLocation location) public async Task<T> LoadAsync<T>(IDataLocation location)
where T : class, IData, new() where T : class, IData, new()
{ {
await EnsureLoadedAsync(); await EnsureLoadedAsync().ConfigureAwait(false);
var key = location.Key; var key = location.Key;
var result = _file!.Sections.TryGetValue(key, out var raw) ? Serializer.Deserialize<T>(raw) : new T(); var result = _file!.Sections.TryGetValue(key, out var raw) ? Serializer.Deserialize<T>(raw) : new T();
if (_options.EnableEvents) if (_options.EnableEvents)
@ -85,8 +85,9 @@ public class UnifiedSettingsDataRepository(
public async Task SaveAsync<T>(IDataLocation location, T data) public async Task SaveAsync<T>(IDataLocation location, T data)
where T : class, IData where T : class, IData
{ {
await EnsureLoadedAsync(); await EnsureLoadedAsync().ConfigureAwait(false);
await MutateAndPersistAsync(file => file.Sections[location.Key] = Serializer.Serialize(data)); await MutateAndPersistAsync(file => file.Sections[location.Key] = Serializer.Serialize(data))
.ConfigureAwait(false);
if (_options.EnableEvents) if (_options.EnableEvents)
{ {
@ -101,7 +102,7 @@ public class UnifiedSettingsDataRepository(
/// <returns>如果数据存在则返回true否则返回false</returns> /// <returns>如果数据存在则返回true否则返回false</returns>
public async Task<bool> ExistsAsync(IDataLocation location) public async Task<bool> ExistsAsync(IDataLocation location)
{ {
await EnsureLoadedAsync(); await EnsureLoadedAsync().ConfigureAwait(false);
return File.Sections.ContainsKey(location.Key); return File.Sections.ContainsKey(location.Key);
} }
@ -112,10 +113,10 @@ public class UnifiedSettingsDataRepository(
/// <returns>异步操作任务</returns> /// <returns>异步操作任务</returns>
public async Task DeleteAsync(IDataLocation location) public async Task DeleteAsync(IDataLocation location)
{ {
await EnsureLoadedAsync(); await EnsureLoadedAsync().ConfigureAwait(false);
var removed = false; var removed = false;
await _lock.WaitAsync(); await _lock.WaitAsync().ConfigureAwait(false);
try try
{ {
var currentFile = File; var currentFile = File;
@ -126,7 +127,7 @@ public class UnifiedSettingsDataRepository(
return; return;
} }
await WriteUnifiedFileCoreAsync(currentFile, nextFile); await WriteUnifiedFileCoreAsync(currentFile, nextFile).ConfigureAwait(false);
_file = nextFile; _file = nextFile;
} }
finally finally
@ -148,17 +149,18 @@ public class UnifiedSettingsDataRepository(
public async Task SaveAllAsync( public async Task SaveAllAsync(
IEnumerable<(IDataLocation location, IData data)> dataList) IEnumerable<(IDataLocation location, IData data)> dataList)
{ {
await EnsureLoadedAsync(); await EnsureLoadedAsync().ConfigureAwait(false);
var valueTuples = dataList.ToList(); var valueTuples = dataList.ToList();
await MutateAndPersistAsync(file => await MutateAndPersistAsync(file =>
{
foreach (var (location, data) in valueTuples)
{ {
file.Sections[location.Key] = Serializer.Serialize(data); foreach (var (location, data) in valueTuples)
} {
}); file.Sections[location.Key] = Serializer.Serialize(data);
}
})
.ConfigureAwait(false);
if (_options.EnableEvents) if (_options.EnableEvents)
this.SendEvent(new DataBatchSavedEvent(valueTuples)); this.SendEvent(new DataBatchSavedEvent(valueTuples));
@ -170,9 +172,9 @@ public class UnifiedSettingsDataRepository(
/// <returns>包含所有数据项的字典,键为数据位置键,值为数据对象</returns> /// <returns>包含所有数据项的字典,键为数据位置键,值为数据对象</returns>
public async Task<IDictionary<string, IData>> LoadAllAsync() public async Task<IDictionary<string, IData>> LoadAllAsync()
{ {
await EnsureLoadedAsync(); await EnsureLoadedAsync().ConfigureAwait(false);
var result = new Dictionary<string, IData>(); var result = new Dictionary<string, IData>(StringComparer.Ordinal);
foreach (var (key, raw) in File.Sections) foreach (var (key, raw) in File.Sections)
{ {
@ -216,15 +218,15 @@ public class UnifiedSettingsDataRepository(
{ {
if (_loaded) return; if (_loaded) return;
await _lock.WaitAsync(); await _lock.WaitAsync().ConfigureAwait(false);
try try
{ {
if (_loaded) return; if (_loaded) return;
var key = UnifiedKey; var key = UnifiedKey;
_file = await Storage.ExistsAsync(key) _file = await Storage.ExistsAsync(key).ConfigureAwait(false)
? await Storage.ReadAsync<UnifiedSettingsFile>(key) ? await Storage.ReadAsync<UnifiedSettingsFile>(key).ConfigureAwait(false)
: new UnifiedSettingsFile { Version = 1 }; : new UnifiedSettingsFile { Version = 1 };
_loaded = true; _loaded = true;
@ -241,7 +243,7 @@ public class UnifiedSettingsDataRepository(
/// </summary> /// </summary>
private async Task MutateAndPersistAsync(Action<UnifiedSettingsFile> mutation) private async Task MutateAndPersistAsync(Action<UnifiedSettingsFile> mutation)
{ {
await _lock.WaitAsync(); await _lock.WaitAsync().ConfigureAwait(false);
try try
{ {
var currentFile = File; var currentFile = File;
@ -250,7 +252,7 @@ public class UnifiedSettingsDataRepository(
// 先在副本上计算“下一份已提交状态”,只有底层持久化成功后才交换缓存, // 先在副本上计算“下一份已提交状态”,只有底层持久化成功后才交换缓存,
// 这样即使备份或写入失败,也不会把未提交修改留在内存快照里。 // 这样即使备份或写入失败,也不会把未提交修改留在内存快照里。
mutation(nextFile); mutation(nextFile);
await WriteUnifiedFileCoreAsync(currentFile, nextFile); await WriteUnifiedFileCoreAsync(currentFile, nextFile).ConfigureAwait(false);
_file = nextFile; _file = nextFile;
} }
finally finally
@ -270,13 +272,13 @@ public class UnifiedSettingsDataRepository(
/// <param name="nextFile">即将提交的新统一文件快照。</param> /// <param name="nextFile">即将提交的新统一文件快照。</param>
private async Task WriteUnifiedFileCoreAsync(UnifiedSettingsFile currentFile, UnifiedSettingsFile nextFile) private async Task WriteUnifiedFileCoreAsync(UnifiedSettingsFile currentFile, UnifiedSettingsFile nextFile)
{ {
if (_options.AutoBackup && await Storage.ExistsAsync(UnifiedKey)) if (_options.AutoBackup && await Storage.ExistsAsync(UnifiedKey).ConfigureAwait(false))
{ {
var backupKey = $"{UnifiedKey}.backup"; var backupKey = $"{UnifiedKey}.backup";
await Storage.WriteAsync(backupKey, currentFile); await Storage.WriteAsync(backupKey, currentFile).ConfigureAwait(false);
} }
await Storage.WriteAsync(UnifiedKey, nextFile); await Storage.WriteAsync(UnifiedKey, nextFile).ConfigureAwait(false);
} }
/// <summary> /// <summary>

View File

@ -82,7 +82,7 @@ public abstract class SceneRouterBase
string sceneKey, string sceneKey,
ISceneEnterParam? param = null) ISceneEnterParam? param = null)
{ {
await _transitionLock.WaitAsync(); await _transitionLock.WaitAsync().ConfigureAwait(true);
try try
{ {
IsTransitioning = true; IsTransitioning = true;
@ -184,7 +184,7 @@ public abstract class SceneRouterBase
string sceneKey, string sceneKey,
ISceneEnterParam? param = null) ISceneEnterParam? param = null)
{ {
await _transitionLock.WaitAsync(); await _transitionLock.WaitAsync().ConfigureAwait(true);
try try
{ {
IsTransitioning = true; IsTransitioning = true;
@ -220,7 +220,7 @@ public abstract class SceneRouterBase
} }
// 守卫检查 // 守卫检查
if (!await ExecuteEnterGuardsAsync(sceneKey, param)) if (!await ExecuteEnterGuardsAsync(sceneKey, param).ConfigureAwait(true))
{ {
Log.Warn("Push blocked by guard: {0}", sceneKey); Log.Warn("Push blocked by guard: {0}", sceneKey);
return; return;
@ -233,20 +233,20 @@ public abstract class SceneRouterBase
Root!.AddScene(scene); Root!.AddScene(scene);
// 加载资源 // 加载资源
await scene.OnLoadAsync(param); await scene.OnLoadAsync(param).ConfigureAwait(true);
// 暂停当前场景 // 暂停当前场景
if (Stack.Count > 0) if (Stack.Count > 0)
{ {
var current = Stack.Peek(); var current = Stack.Peek();
await current.OnPauseAsync(); await current.OnPauseAsync().ConfigureAwait(true);
} }
// 压入栈 // 压入栈
Stack.Push(scene); Stack.Push(scene);
// 进入场景 // 进入场景
await scene.OnEnterAsync(); await scene.OnEnterAsync().ConfigureAwait(true);
Log.Debug("Push Scene: {0}, stackCount={1}", Log.Debug("Push Scene: {0}, stackCount={1}",
sceneKey, Stack.Count); sceneKey, Stack.Count);
@ -262,7 +262,7 @@ public abstract class SceneRouterBase
/// <returns>异步任务。</returns> /// <returns>异步任务。</returns>
public async ValueTask PopAsync() public async ValueTask PopAsync()
{ {
await _transitionLock.WaitAsync(); await _transitionLock.WaitAsync().ConfigureAwait(true);
try try
{ {
IsTransitioning = true; IsTransitioning = true;
@ -293,7 +293,7 @@ public abstract class SceneRouterBase
var top = Stack.Peek(); var top = Stack.Peek();
// 守卫检查 // 守卫检查
if (!await ExecuteLeaveGuardsAsync(top.Key)) if (!await ExecuteLeaveGuardsAsync(top.Key).ConfigureAwait(true))
{ {
Log.Warn("Pop blocked by guard: {0}", top.Key); Log.Warn("Pop blocked by guard: {0}", top.Key);
return; return;
@ -302,10 +302,10 @@ public abstract class SceneRouterBase
Stack.Pop(); Stack.Pop();
// 退出场景 // 退出场景
await top.OnExitAsync(); await top.OnExitAsync().ConfigureAwait(true);
// 卸载资源 // 卸载资源
await top.OnUnloadAsync(); await top.OnUnloadAsync().ConfigureAwait(true);
// 从场景树移除 // 从场景树移除
Root!.RemoveScene(top); Root!.RemoveScene(top);
@ -314,7 +314,7 @@ public abstract class SceneRouterBase
if (Stack.Count > 0) if (Stack.Count > 0)
{ {
var next = Stack.Peek(); var next = Stack.Peek();
await next.OnResumeAsync(); await next.OnResumeAsync().ConfigureAwait(true);
} }
Log.Debug("Pop Scene, stackCount={0}", Stack.Count); Log.Debug("Pop Scene, stackCount={0}", Stack.Count);
@ -330,7 +330,7 @@ public abstract class SceneRouterBase
/// <returns>异步任务。</returns> /// <returns>异步任务。</returns>
public async ValueTask ClearAsync() public async ValueTask ClearAsync()
{ {
await _transitionLock.WaitAsync(); await _transitionLock.WaitAsync().ConfigureAwait(true);
try try
{ {
IsTransitioning = true; IsTransitioning = true;