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

View File

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

View File

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

View File

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