From e3eec5452cc658ba80088c2256568058939c7748 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 27 Apr 2026 07:41:10 +0800 Subject: [PATCH] =?UTF-8?q?fix(game):=20=E4=BF=AE=E5=A4=8D=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=BB=93=E5=BA=93=E4=B8=8E=E5=9C=BA=E6=99=AF=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E5=88=86=E6=9E=90=E5=99=A8=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复数据仓库异步存储调用的 ConfigureAwait(false) 使用,消除目标 MA0004 警告 - 更新 UnifiedSettingsDataRepository 的字符串键字典 comparer 为 StringComparer.Ordinal,消除目标 MA0002 警告 - 保留场景切换流程在当前上下文继续执行,并显式使用 ConfigureAwait(true) 说明上下文约束 --- GFramework.Game/Data/DataRepository.cs | 22 ++++---- GFramework.Game/Data/SaveRepository.cs | 22 ++++---- .../Data/UnifiedSettingsDataRepository.cs | 50 ++++++++++--------- GFramework.Game/Scene/SceneRouterBase.cs | 24 ++++----- 4 files changed, 61 insertions(+), 57 deletions(-) diff --git a/GFramework.Game/Data/DataRepository.cs b/GFramework.Game/Data/DataRepository.cs index b4820119..68aae856 100644 --- a/GFramework.Game/Data/DataRepository.cs +++ b/GFramework.Game/Data/DataRepository.cs @@ -52,7 +52,9 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options = var key = location.ToStorageKey(); // 检查存储中是否存在指定键的数据 - T result = await Storage.ExistsAsync(key) ? await Storage.ReadAsync(key) : new T(); + T result = await Storage.ExistsAsync(key).ConfigureAwait(false) + ? await Storage.ReadAsync(key).ConfigureAwait(false) + : new T(); // 如果启用事件功能,则发送数据加载完成事件 if (_options.EnableEvents) @@ -70,7 +72,7 @@ public class DataRepository(IStorage? storage, DataRepositoryOptions? options = public async Task SaveAsync(IDataLocation location, T data) where T : class, IData { - await SaveCoreAsync(location, data, emitSavedEvent: true); + await SaveCoreAsync(location, data, emitSavedEvent: true).ConfigureAwait(false); } /// @@ -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(key); - await Storage.WriteAsync(key, data); + await BackupIfNeededAsync(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(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(key); - await Storage.WriteAsync(backupKey, existing); + var existing = await Storage.ReadAsync(key).ConfigureAwait(false); + await Storage.WriteAsync(backupKey, existing).ConfigureAwait(false); } /// diff --git a/GFramework.Game/Data/SaveRepository.cs b/GFramework.Game/Data/SaveRepository.cs index f237468d..0c1f936c 100644 --- a/GFramework.Game/Data/SaveRepository.cs +++ b/GFramework.Game/Data/SaveRepository.cs @@ -99,7 +99,7 @@ public class SaveRepository : AbstractContextUtility, ISaveRepository public async Task ExistsAsync(int slot) { var storage = GetSlotStorage(slot); - return await storage.ExistsAsync(_config.SaveFileName); + return await storage.ExistsAsync(_config.SaveFileName).ConfigureAwait(false); } /// @@ -111,10 +111,10 @@ public class SaveRepository : 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(_config.SaveFileName); - return await MigrateIfNeededAsync(slot, storage, loaded); + var loaded = await storage.ReadAsync(_config.SaveFileName).ConfigureAwait(false); + return await MigrateIfNeededAsync(slot, storage, loaded).ConfigureAwait(false); } return new TSaveData(); @@ -130,11 +130,11 @@ public class SaveRepository : 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); } /// @@ -144,7 +144,7 @@ public class SaveRepository : AbstractContextUtility, ISaveRepository public async Task DeleteAsync(int slot) { var storage = GetSlotStorage(slot); - await storage.DeleteAsync(_config.SaveFileName); + await storage.DeleteAsync(_config.SaveFileName).ConfigureAwait(false); } /// @@ -154,7 +154,7 @@ public class SaveRepository : AbstractContextUtility, ISaveRepository public async Task> ListSlotsAsync() { // 列举所有槽位目录 - var directories = await _rootStorage.ListDirectoriesAsync(); + var directories = await _rootStorage.ListDirectoriesAsync().ConfigureAwait(false); var slots = new List(); @@ -171,7 +171,7 @@ public class SaveRepository : 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 : 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; } diff --git a/GFramework.Game/Data/UnifiedSettingsDataRepository.cs b/GFramework.Game/Data/UnifiedSettingsDataRepository.cs index 99672301..e1fdd7cc 100644 --- a/GFramework.Game/Data/UnifiedSettingsDataRepository.cs +++ b/GFramework.Game/Data/UnifiedSettingsDataRepository.cs @@ -37,7 +37,7 @@ public class UnifiedSettingsDataRepository( { private readonly SemaphoreSlim _lock = new(1, 1); private readonly DataRepositoryOptions _options = options ?? new DataRepositoryOptions(); - private readonly Dictionary _typeRegistry = new(); + private readonly Dictionary _typeRegistry = new(StringComparer.Ordinal); private UnifiedSettingsFile? _file; private bool _loaded; private IRuntimeTypeSerializer? _serializer = serializer; @@ -67,7 +67,7 @@ public class UnifiedSettingsDataRepository( public async Task LoadAsync(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(raw) : new T(); if (_options.EnableEvents) @@ -85,8 +85,9 @@ public class UnifiedSettingsDataRepository( public async Task SaveAsync(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( /// 如果数据存在则返回true,否则返回false public async Task ExistsAsync(IDataLocation location) { - await EnsureLoadedAsync(); + await EnsureLoadedAsync().ConfigureAwait(false); return File.Sections.ContainsKey(location.Key); } @@ -112,10 +113,10 @@ public class UnifiedSettingsDataRepository( /// 异步操作任务 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( /// 包含所有数据项的字典,键为数据位置键,值为数据对象 public async Task> LoadAllAsync() { - await EnsureLoadedAsync(); + await EnsureLoadedAsync().ConfigureAwait(false); - var result = new Dictionary(); + var result = new Dictionary(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(key) + _file = await Storage.ExistsAsync(key).ConfigureAwait(false) + ? await Storage.ReadAsync(key).ConfigureAwait(false) : new UnifiedSettingsFile { Version = 1 }; _loaded = true; @@ -241,7 +243,7 @@ public class UnifiedSettingsDataRepository( /// private async Task MutateAndPersistAsync(Action 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( /// 即将提交的新统一文件快照。 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); } /// diff --git a/GFramework.Game/Scene/SceneRouterBase.cs b/GFramework.Game/Scene/SceneRouterBase.cs index 9c7450ce..93f43379 100644 --- a/GFramework.Game/Scene/SceneRouterBase.cs +++ b/GFramework.Game/Scene/SceneRouterBase.cs @@ -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 /// 异步任务。 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 /// 异步任务。 public async ValueTask ClearAsync() { - await _transitionLock.WaitAsync(); + await _transitionLock.WaitAsync().ConfigureAwait(true); try { IsTransitioning = true;