From a7a3eca40dff3c10e400a805dadc3b8f34c177ab Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:26:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(pr-review):=20=E6=94=B6=E6=95=9BPR=E5=BB=BA?= =?UTF-8?q?=E8=AE=AE=E5=B9=B6=E4=BF=AE=E5=A4=8D=E6=9E=84=E5=BB=BA=E9=AA=8C?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 PR #288 中经本地复核后仍成立的 Core、Game 与测试建议 - 更新 WSL 标准 dotnet build 验证路径并确认 Release 构建可通过 - 补充 analyzer-warning-reduction 跟踪文档记录本轮结论与恢复点 --- .../ArchitectureServicesTests.cs | 8 ++-- .../Architectures/GameContextTests.cs | 20 ++++---- .../RegistryInitializationHookBaseTests.cs | 2 +- .../Coroutine/TaskCoroutineExtensionsTests.cs | 2 - .../Coroutine/WaitForTaskTests.cs | 3 +- .../Logging/RollingFileAppenderTests.cs | 2 +- GFramework.Core/Extensions/AsyncExtensions.cs | 21 ++------- .../ContextAwareCommandExtensions.cs | 44 ++++-------------- .../Extensions/ContextAwareEventExtensions.cs | 38 +++------------ .../Extensions/ContextAwareQueryExtensions.cs | 22 ++------- .../Extensions/NumericExtensions.cs | 17 ++----- .../Extensions/StringExtensions.cs | 22 ++------- .../StateManagement/StoreBuilder.cs | 15 ++---- .../StateManagement/StoreSelection.cs | 11 +---- GFramework.Game/Scene/SceneRouterBase.cs | 41 +++++++++-------- GFramework.Game/Storage/FileStorage.cs | 4 +- GFramework.Game/Storage/ScopedStorage.cs | 4 +- .../analyzer-warning-reduction-tracking.md | 46 +++++++++++++------ .../analyzer-warning-reduction-trace.md | 33 +++++++++++++ 19 files changed, 143 insertions(+), 212 deletions(-) diff --git a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs index 4a520097..f1474403 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs @@ -366,7 +366,7 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 要发送的命令。 /// 取消令牌。 /// 命令响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendCommandAsync( GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) @@ -380,7 +380,7 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 命令响应类型。 /// 要发送的命令。 /// 命令响应。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command) { throw new NotSupportedException(); @@ -393,7 +393,7 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 要发送的查询。 /// 取消令牌。 /// 查询结果任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendQueryAsync( GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) @@ -407,7 +407,7 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 查询结果类型。 /// 要发送的查询。 /// 查询结果。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query) { throw new NotSupportedException(); diff --git a/GFramework.Core.Tests/Architectures/GameContextTests.cs b/GFramework.Core.Tests/Architectures/GameContextTests.cs index 4f9b99e3..fd6e2bbb 100644 --- a/GFramework.Core.Tests/Architectures/GameContextTests.cs +++ b/GFramework.Core.Tests/Architectures/GameContextTests.cs @@ -401,7 +401,7 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的请求。 /// 取消令牌。 /// 请求响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendRequestAsync(IRequest request, CancellationToken cancellationToken = default) { @@ -414,7 +414,7 @@ public class TestArchitectureContext : IArchitectureContext /// 响应类型。 /// 要发送的请求。 /// 请求响应。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendRequest(IRequest request) { throw new NotSupportedException(); @@ -427,7 +427,7 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的命令。 /// 取消令牌。 /// 命令响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendCommandAsync( GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) @@ -441,7 +441,7 @@ public class TestArchitectureContext : IArchitectureContext /// 命令响应类型。 /// 要发送的命令。 /// 命令响应。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command) { throw new NotSupportedException(); @@ -454,7 +454,7 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的查询。 /// 取消令牌。 /// 查询结果任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendQueryAsync( GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) @@ -468,7 +468,7 @@ public class TestArchitectureContext : IArchitectureContext /// 查询结果类型。 /// 要发送的查询。 /// 查询结果。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query) { throw new NotSupportedException(); @@ -481,7 +481,7 @@ public class TestArchitectureContext : IArchitectureContext /// 要发布的通知。 /// 取消令牌。 /// 通知发布任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { @@ -495,7 +495,7 @@ public class TestArchitectureContext : IArchitectureContext /// 流式请求。 /// 取消令牌。 /// 异步响应流。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public IAsyncEnumerable CreateStream( IStreamRequest request, CancellationToken cancellationToken = default) @@ -510,7 +510,7 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的命令。 /// 取消令牌。 /// 命令发送任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) where TCommand : IRequest { @@ -524,7 +524,7 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的请求。 /// 取消令牌。 /// 请求响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendAsync(IRequest command, CancellationToken cancellationToken = default) { diff --git a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs index 4fdb4398..b3a3975f 100644 --- a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs +++ b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs @@ -287,7 +287,7 @@ public class TestArchitectureContextWithRegistry : TestArchitectureContext _registry = registry; } - public override TUtility GetUtility() + public override TUtility? GetUtility() where TUtility : class { if (typeof(TUtility) == typeof(TestRegistry)) { diff --git a/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs index 012fa13e..9c46223c 100644 --- a/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs @@ -66,8 +66,6 @@ public class TaskCoroutineExtensionsTests var task = Task.FromResult(42); var instruction = task.AsCoroutineInstruction(); - task.ConfigureAwait(false).GetAwaiter().GetResult(); - Assert.That(instruction.Result, Is.EqualTo(42)); } diff --git a/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs b/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs index 73789a5d..c8d50b39 100644 --- a/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs +++ b/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs @@ -291,8 +291,7 @@ public class WaitForTaskTests var wait = new WaitForTask(task); await task.ConfigureAwait(false); - - Task.Delay(100).Wait(); + await Task.Delay(100).ConfigureAwait(false); Assert.That(wait.IsDone, Is.True); Assert.That(wait.Result, Is.EqualTo(expectedValue)); diff --git a/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs b/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs index e487f188..5cce91e3 100644 --- a/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs +++ b/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs @@ -68,7 +68,7 @@ public class RollingFileAppenderTests } // 检查是否生成了多个文件 - var files = Directory.GetFiles(_testDir, "*.log").OrderBy(f => f, System.StringComparer.Ordinal).ToArray(); + var files = Directory.GetFiles(_testDir, "*.log"); Assert.That(files.Length, Is.GreaterThan(1)); } diff --git a/GFramework.Core/Extensions/AsyncExtensions.cs b/GFramework.Core/Extensions/AsyncExtensions.cs index 246252ca..47124086 100644 --- a/GFramework.Core/Extensions/AsyncExtensions.cs +++ b/GFramework.Core/Extensions/AsyncExtensions.cs @@ -28,10 +28,7 @@ public static class AsyncExtensions TimeSpan timeout, CancellationToken cancellationToken = default) { - if (taskFactory is null) - { - throw new ArgumentNullException(nameof(taskFactory)); - } + ArgumentNullException.ThrowIfNull(taskFactory); // linkedCts 同时响应:超时 + 外部取消 using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); @@ -74,10 +71,7 @@ public static class AsyncExtensions TimeSpan timeout, CancellationToken cancellationToken = default) { - if (taskFactory is null) - { - throw new ArgumentNullException(nameof(taskFactory)); - } + ArgumentNullException.ThrowIfNull(taskFactory); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); linkedCts.CancelAfter(timeout); @@ -119,15 +113,8 @@ public static class AsyncExtensions /// public static async Task WithFallbackAsync(this Task task, Func fallback) { - if (task is null) - { - throw new ArgumentNullException(nameof(task)); - } - - if (fallback is null) - { - throw new ArgumentNullException(nameof(fallback)); - } + ArgumentNullException.ThrowIfNull(task); + ArgumentNullException.ThrowIfNull(fallback); try { diff --git a/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs b/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs index 5a98d179..49676e56 100644 --- a/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareCommandExtensions.cs @@ -19,15 +19,8 @@ public static class ContextAwareCommandExtensions public static TResult SendCommand(this IContextAware contextAware, ICommand command) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (command is null) - { - throw new ArgumentNullException(nameof(command)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); var context = contextAware.GetContext(); return context.SendCommand(command); @@ -41,15 +34,8 @@ public static class ContextAwareCommandExtensions /// 当 contextAware 或 command 为 null 时抛出 public static void SendCommand(this IContextAware contextAware, ICommand command) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (command is null) - { - throw new ArgumentNullException(nameof(command)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); var context = contextAware.GetContext(); context.SendCommand(command); @@ -64,15 +50,8 @@ public static class ContextAwareCommandExtensions /// 当 contextAware 或 command 为 null 时抛出 public static async Task SendCommandAsync(this IContextAware contextAware, IAsyncCommand command) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (command is null) - { - throw new ArgumentNullException(nameof(command)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); var context = contextAware.GetContext(); await context.SendCommandAsync(command).ConfigureAwait(false); @@ -89,15 +68,8 @@ public static class ContextAwareCommandExtensions public static async Task SendCommandAsync(this IContextAware contextAware, IAsyncCommand command) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (command is null) - { - throw new ArgumentNullException(nameof(command)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(command); var context = contextAware.GetContext(); return await context.SendCommandAsync(command).ConfigureAwait(false); diff --git a/GFramework.Core/Extensions/ContextAwareEventExtensions.cs b/GFramework.Core/Extensions/ContextAwareEventExtensions.cs index f1e35732..491c5230 100644 --- a/GFramework.Core/Extensions/ContextAwareEventExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareEventExtensions.cs @@ -16,10 +16,7 @@ public static class ContextAwareEventExtensions /// 当 contextAware 为 null 时抛出 public static void SendEvent(this IContextAware contextAware) where TEvent : new() { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } + ArgumentNullException.ThrowIfNull(contextAware); var context = contextAware.GetContext(); context.SendEvent(); @@ -34,15 +31,8 @@ public static class ContextAwareEventExtensions /// 当 contextAware 或 e 为 null 时抛出 public static void SendEvent(this IContextAware contextAware, TEvent e) where TEvent : class { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (e is null) - { - throw new ArgumentNullException(nameof(e)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(e); var context = contextAware.GetContext(); context.SendEvent(e); @@ -57,15 +47,8 @@ public static class ContextAwareEventExtensions /// 事件注销接口 public static IUnRegister RegisterEvent(this IContextAware contextAware, Action handler) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (handler is null) - { - throw new ArgumentNullException(nameof(handler)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(handler); var context = contextAware.GetContext(); return context.RegisterEvent(handler); @@ -79,15 +62,8 @@ public static class ContextAwareEventExtensions /// 之前绑定的事件处理器 public static void UnRegisterEvent(this IContextAware contextAware, Action onEvent) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (onEvent is null) - { - throw new ArgumentNullException(nameof(onEvent)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(onEvent); var context = contextAware.GetContext(); context.UnRegisterEvent(onEvent); diff --git a/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs b/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs index 07e6aa7a..63ad260c 100644 --- a/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareQueryExtensions.cs @@ -18,15 +18,8 @@ public static class ContextAwareQueryExtensions /// 当 contextAware 或 query 为 null 时抛出 public static TResult SendQuery(this IContextAware contextAware, IQuery query) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(query); var context = contextAware.GetContext(); return context.SendQuery(query); @@ -44,15 +37,8 @@ public static class ContextAwareQueryExtensions public static async Task SendQueryAsync(this IContextAware contextAware, IAsyncQuery query) { - if (contextAware is null) - { - throw new ArgumentNullException(nameof(contextAware)); - } - - if (query is null) - { - throw new ArgumentNullException(nameof(query)); - } + ArgumentNullException.ThrowIfNull(contextAware); + ArgumentNullException.ThrowIfNull(query); var context = contextAware.GetContext(); return await context.SendQueryAsync(query).ConfigureAwait(false); diff --git a/GFramework.Core/Extensions/NumericExtensions.cs b/GFramework.Core/Extensions/NumericExtensions.cs index c00f0044..c0637310 100644 --- a/GFramework.Core/Extensions/NumericExtensions.cs +++ b/GFramework.Core/Extensions/NumericExtensions.cs @@ -25,20 +25,9 @@ public static class NumericExtensions /// public static bool Between(this T value, T min, T max, bool inclusive = true) where T : IComparable { - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (min is null) - { - throw new ArgumentNullException(nameof(min)); - } - - if (max is null) - { - throw new ArgumentNullException(nameof(max)); - } + ArgumentNullException.ThrowIfNull(value); + ArgumentNullException.ThrowIfNull(min); + ArgumentNullException.ThrowIfNull(max); if (min.CompareTo(max) > 0) { diff --git a/GFramework.Core/Extensions/StringExtensions.cs b/GFramework.Core/Extensions/StringExtensions.cs index 616c3a83..914dc855 100644 --- a/GFramework.Core/Extensions/StringExtensions.cs +++ b/GFramework.Core/Extensions/StringExtensions.cs @@ -38,15 +38,8 @@ public static class StringExtensions /// public static string Truncate(this string str, int maxLength, string suffix = "...") { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - if (suffix is null) - { - throw new ArgumentNullException(nameof(suffix)); - } + ArgumentNullException.ThrowIfNull(str); + ArgumentNullException.ThrowIfNull(suffix); if (maxLength < suffix.Length) { @@ -77,15 +70,8 @@ public static class StringExtensions /// public static string Join(this IEnumerable values, string separator) { - if (values is null) - { - throw new ArgumentNullException(nameof(values)); - } - - if (separator is null) - { - throw new ArgumentNullException(nameof(separator)); - } + ArgumentNullException.ThrowIfNull(values); + ArgumentNullException.ThrowIfNull(separator); return string.Join(separator, values); } diff --git a/GFramework.Core/StateManagement/StoreBuilder.cs b/GFramework.Core/StateManagement/StoreBuilder.cs index bf2565ea..50043a11 100644 --- a/GFramework.Core/StateManagement/StoreBuilder.cs +++ b/GFramework.Core/StateManagement/StoreBuilder.cs @@ -39,10 +39,7 @@ public sealed class StoreBuilder : IStoreBuilder /// 当前构建器实例。 public IStoreBuilder UseMiddleware(IStoreMiddleware middleware) { - if (middleware is null) - { - throw new ArgumentNullException(nameof(middleware)); - } + ArgumentNullException.ThrowIfNull(middleware); _configurators.Add(store => store.UseMiddleware(middleware)); return this; @@ -112,10 +109,7 @@ public sealed class StoreBuilder : IStoreBuilder /// 当前构建器实例。 public IStoreBuilder AddReducer(Func reducer) { - if (reducer is null) - { - throw new ArgumentNullException(nameof(reducer)); - } + ArgumentNullException.ThrowIfNull(reducer); _configurators.Add(store => store.RegisterReducer(reducer)); return this; @@ -129,10 +123,7 @@ public sealed class StoreBuilder : IStoreBuilder /// 当前构建器实例。 public IStoreBuilder AddReducer(IReducer reducer) { - if (reducer is null) - { - throw new ArgumentNullException(nameof(reducer)); - } + ArgumentNullException.ThrowIfNull(reducer); _configurators.Add(store => store.RegisterReducer(reducer)); return this; diff --git a/GFramework.Core/StateManagement/StoreSelection.cs b/GFramework.Core/StateManagement/StoreSelection.cs index 867831b0..0f417409 100644 --- a/GFramework.Core/StateManagement/StoreSelection.cs +++ b/GFramework.Core/StateManagement/StoreSelection.cs @@ -24,18 +24,11 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// private readonly List _listeners = []; - /// - /// 保护监听器集合和底层 Store 订阅句柄的同步锁。 - /// #if NET9_0_OR_GREATER - /// - /// net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。 - /// + // net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。 private readonly System.Threading.Lock _lock = new(); #else - /// - /// net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。 - /// + // net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。 private readonly object _lock = new(); #endif diff --git a/GFramework.Game/Scene/SceneRouterBase.cs b/GFramework.Game/Scene/SceneRouterBase.cs index eb7ce1a5..9c7450ce 100644 --- a/GFramework.Game/Scene/SceneRouterBase.cs +++ b/GFramework.Game/Scene/SceneRouterBase.cs @@ -193,7 +193,7 @@ public abstract class SceneRouterBase await _pipeline.ExecuteAroundAsync( @event, - () => ExecutePushCoreAsync(@event, sceneKey, param)).ConfigureAwait(false); + () => ExecutePushCoreAsync(@event, sceneKey, param)).ConfigureAwait(true); } finally { @@ -271,7 +271,7 @@ public abstract class SceneRouterBase await _pipeline.ExecuteAroundAsync( @event, - () => ExecutePopCoreAsync(@event)).ConfigureAwait(false); + () => ExecutePopCoreAsync(@event)).ConfigureAwait(true); } finally { @@ -339,7 +339,7 @@ public abstract class SceneRouterBase await _pipeline.ExecuteAroundAsync( @event, - () => ExecuteClearCoreAsync(@event)).ConfigureAwait(false); + () => ExecuteClearCoreAsync(@event)).ConfigureAwait(true); } finally { @@ -357,7 +357,7 @@ public abstract class SceneRouterBase { while (Stack.Count > 0) { - await PopInternalAsync(); + await PopInternalAsync().ConfigureAwait(true); } } @@ -365,6 +365,8 @@ public abstract class SceneRouterBase #region Helper Methods + // Scene 生命周期回调和 pipeline handlers 可能依赖引擎线程,因此这些核心切换顺序统一显式保留上下文。 + /// /// 执行 Replace 的核心切换顺序。 /// @@ -377,11 +379,10 @@ public abstract class SceneRouterBase string sceneKey, ISceneEnterParam? param) { - // 场景生命周期回调可能依赖引擎线程,因此这里保留默认 await 行为。 - await BeforeChangeAsync(@event); - await ClearInternalAsync(); - await PushInternalAsync(sceneKey, param); - await AfterChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); + await ClearInternalAsync().ConfigureAwait(true); + await PushInternalAsync(sceneKey, param).ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); } /// @@ -396,9 +397,9 @@ public abstract class SceneRouterBase string sceneKey, ISceneEnterParam? param) { - await BeforeChangeAsync(@event); - await PushInternalAsync(sceneKey, param); - await AfterChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); + await PushInternalAsync(sceneKey, param).ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); } /// @@ -408,9 +409,9 @@ public abstract class SceneRouterBase /// 异步任务。 private async Task ExecutePopCoreAsync(SceneTransitionEvent @event) { - await BeforeChangeAsync(@event); - await PopInternalAsync(); - await AfterChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); + await PopInternalAsync().ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); } /// @@ -420,9 +421,9 @@ public abstract class SceneRouterBase /// 异步任务。 private async Task ExecuteClearCoreAsync(SceneTransitionEvent @event) { - await BeforeChangeAsync(@event); - await ClearInternalAsync(); - await AfterChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); + await ClearInternalAsync().ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); } /// @@ -454,7 +455,7 @@ public abstract class SceneRouterBase private async Task BeforeChangeAsync(SceneTransitionEvent @event) { Log.Debug("BeforeChange phases started: {0}", @event.TransitionType); - await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange).ConfigureAwait(false); + await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange).ConfigureAwait(true); Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType); } @@ -465,7 +466,7 @@ public abstract class SceneRouterBase private async Task AfterChangeAsync(SceneTransitionEvent @event) { Log.Debug("AfterChange phases started: {0}", @event.TransitionType); - await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange).ConfigureAwait(false); + await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange).ConfigureAwait(true); Log.Debug("AfterChange phases completed: {0}", @event.TransitionType); } diff --git a/GFramework.Game/Storage/FileStorage.cs b/GFramework.Game/Storage/FileStorage.cs index 6d819f34..a1c3091e 100644 --- a/GFramework.Game/Storage/FileStorage.cs +++ b/GFramework.Game/Storage/FileStorage.cs @@ -251,7 +251,7 @@ public sealed class FileStorage : IFileStorage, IDisposable useAsync: true); await using var configuredFileStream = fs.ConfigureAwait(false); - using var sr = new StreamReader(fs, Encoding.UTF8); + using var sr = new StreamReader(fs, Encoding.UTF8, true, -1, leaveOpen: true); var content = await sr.ReadToEndAsync().ConfigureAwait(false); return _serializer.Deserialize(content); } @@ -373,7 +373,7 @@ public sealed class FileStorage : IFileStorage, IDisposable useAsync: true); await using var configuredFileStream = fs.ConfigureAwait(false); - var sw = new StreamWriter(fs, Encoding.UTF8); + var sw = new StreamWriter(fs, Encoding.UTF8, leaveOpen: true); await using var configuredStreamWriter = sw.ConfigureAwait(false); await sw.WriteAsync(content).ConfigureAwait(false); diff --git a/GFramework.Game/Storage/ScopedStorage.cs b/GFramework.Game/Storage/ScopedStorage.cs index 7ab8c856..c3850a79 100644 --- a/GFramework.Game/Storage/ScopedStorage.cs +++ b/GFramework.Game/Storage/ScopedStorage.cs @@ -100,9 +100,9 @@ public sealed class ScopedStorage(IStorage inner, string prefix) : IScopedStorag /// /// 要删除的键 /// 异步操作任务 - public async Task DeleteAsync(string key) + public Task DeleteAsync(string key) { - await inner.DeleteAsync(Key(key)).ConfigureAwait(false); + return inner.DeleteAsync(Key(key)); } /// diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md index 9130d00b..7ea2c6e7 100644 --- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md +++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md @@ -6,15 +6,16 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-063` -- 当前阶段:`Phase 63` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-064` +- 当前阶段:`Phase 64` - 当前焦点: - - `2026-04-25` 当前 turn 先执行 `$gframework-pr-review`,核对 PR #288 的 latest-head AI review 与本地真实状态 - - 已修复 `GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs` 的 `ConfiguredTaskAwaitable -> Task` 编译错误,并顺手收敛一批同类测试/样式残留 + - `2026-04-25` 当前 turn 先执行 `$gframework-pr-review`,复核 PR #288 的 latest-head unresolved 线程与折叠评论 + - 已收敛一批经本地复核后仍成立的 review 建议,包括 `ThrowIfNull` 回退、测试桩 XML 注释修正、`FileStorage` 资源所有权、`SceneRouterBase` 线程亲和语义与若干测试噪音 + - 已确认用户在 WSL 下直接执行的标准 `dotnet build -c Release` 路径可用;前一轮失败主要来自主线程附加的 workaround 参数而非仓库本身不可构建 - 基线 `origin/main` 仍为 `9964962`(`2026-04-24T23:05:53+08:00`) - 当前累计 branch diff 相对 `origin/main` 为 `75` 个文件、`2098` 行,已触达本轮 `75 files` 阈值 - `RP-061` 之后已接受 2 个批次提交:`03c73a8`、`9ce1fa6` - - 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先先处理 WSL 下 NuGet fallback package folder 指向失效 Windows 路径的构建环境阻塞 + - 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先重新抓取 PR #288 的 unresolved 线程并按最新 head 再做一轮收口 ## 当前活跃事实 @@ -30,6 +31,10 @@ - `PauseStackManagerTests.cs` - 本 turn 结合 PR #288 latest-head review 额外收敛了以下仍然成立的问题: - `AsyncExtensionsTests.cs`:修复 `WithTimeoutAsync` 无返回值测试中错误返回 `ConfiguredTaskAwaitable` 导致的 `CS0029` / `CS1662` + - `ContextAwareCommandExtensions.cs` + - `ContextAwareQueryExtensions.cs` + - `ContextAwareEventExtensions.cs` + - `AsyncExtensions.cs` - `AsyncKeyLockManagerTests.cs`:去掉两处不会产生额外价值的 `Assert.DoesNotThrowAsync(() => Task.WhenAll(...))` 包装,并把取消断言改为直接消费 `ValueTask.AsTask()` - `AsyncArchitectureTests.cs` - `ArchitectureLifecycleBehaviorTests.cs` @@ -39,11 +44,19 @@ - `StringExtensions.cs` - `StoreBuilder.cs` - `StoreSelection.cs` + - `ArchitectureServicesTests.cs` + - `GameContextTests.cs` + - `RollingFileAppenderTests.cs` + - `TaskCoroutineExtensionsTests.cs` + - `WaitForTaskTests.cs` + - `ScopedStorage.cs` + - `FileStorage.cs` + - `SceneRouterBase.cs` - 当前 PR review 观察: - PR:`#288` - - latest reviewed commit:`be336b2088b7c283a140add76d5cff30618ad16d` - - `coderabbitai[bot]` 仍有 `7` 个 open threads,`greptile-apps[bot]` 仍有 `2` 个 open threads - - 本 turn 已优先修复 latest-head 中明确指向 `AsyncExtensionsTests.cs:126` 的 critical 编译错误 + - latest reviewed commit:`70c42b579f70c90ab5461a02e611c0fbd8d8a6f2` + - 抓取时 `coderabbitai[bot]` 有 `6` 个 open threads,`greptile-apps[bot]` 有 `2` 个 open threads + - `Actionable comments posted: 7` 与 `outside diff + nitpick = 19` 并不等于必须全收;本 turn 仅接受经本地复核后仍成立且不与仓库约束冲突的建议 - 本轮 `Core` runtime 低风险机械型清理已落地到: - `AsyncExtensions.cs` - `CollectionExtensions.cs` @@ -64,15 +77,14 @@ ## 当前风险 -- 当前环境下 `GFramework.Core` / `GFramework.Core.Tests` 的 Release build 会命中 `MSB4018`。 - - 直接原因:`ResolvePackageAssets` 仍从历史 restore 生成的 `obj/*.csproj.nuget.g.props` 读取失效的 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages`。 - - 缓解措施:下次恢复时先重建 WSL 原生 restore 元数据,或显式清理并重做 `obj` 下的 NuGet restore 工件,再重新建立可信 build 基线。 - `dotnet clean GFramework.sln -c Release` 与 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 仍无法稳定提供新的 clean 基线。 - 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值。 - 当前 worktree 仍存在未跟踪的 `.codex` 目录。 - 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交。 - 将分支继续推过 `75 files` 会明显降低本轮 reviewability。 - 缓解措施:当前恢复点默认停止;如需继续,建议在新 turn 明确新的文件阈值或先 rebase / refresh baseline。 +- `GFramework.Core`、`GFramework.Game`、`GFramework.Core.Tests` 当前都仍存在模块级历史 warning 基线。 + - 缓解措施:本 turn 已确保本次 touched files 不再引入新的编译错误,并消化了当前 PR review 中仍成立的高信号问题;若要继续 warning reduction,应开新批次按模块系统化收敛。 ## 活跃文档 @@ -100,6 +112,14 @@ - 结果:失败;`MSB4018`,原因同上 - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` - 结果:成功;定位到 PR `#288`,提取 latest-head unresolved AI review threads、MegaLinter 与 Docstring Coverage 信号 +- `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal` + - 结果:成功;已刷新 WSL 原生 restore 元数据,清除先前的 stale fallback package folder 阻塞 +- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` + - 结果:成功;`28 Warning(s)`、`0 Error(s)` +- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release` + - 结果:成功;`329 Warning(s)`、`0 Error(s)` +- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:成功;`137 Warning(s)`、`0 Error(s)` - `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal` - 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,因此不能用 `net8.0` 旁路验证 `Core.Tests` - `git diff --name-only origin/main...HEAD | wc -l` @@ -109,6 +129,6 @@ ## 下一步建议 -1. 当前 turn 已先修复 latest-head PR review 中最紧急的编译错误;后续若继续 PR #288 收尾,优先重新抓取 unresolved threads,确认剩余 8 个 open threads 哪些仍成立。 -2. 若后续要继续 `Core` / `Core.Tests` warning reduction,先修复 WSL 下 stale NuGet restore metadata 导致的 `MSB4018`,再重新建立可信 build 基线。 +1. 当前 turn 已按标准 WSL `dotnet build` 路径完成 `Core` / `Game` / `Core.Tests` Release build 验证;后续若继续 PR #288 收尾,优先重新抓取 unresolved threads,确认哪些线程已可直接 resolve。 +2. 若后续要继续 `Core` / `Core.Tests` / `Game` warning reduction,应以当前标准 build 输出为新真值,而不是继续沿用上一轮带 workaround 参数的失败命令。 3. 若要开启下一轮批处理,优先选择新的 stop-condition(例如新的 file 阈值、warning 目标或限定到单模块)后再继续。 diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md index 5a0916d0..f6394342 100644 --- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md +++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md @@ -1,5 +1,38 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-25 — RP-064 + +### 阶段:按标准 WSL build 路径复核 PR #288 建议并完成本轮收口 + +- 触发背景: + - 用户指出“在 WSL 里直接执行 `dotnet build` 可以成功”,要求主线程按普通路径重新验证,而不是继续使用带 `MSBuildEnableWorkloadResolver=false`、`--no-restore`、手工 `TargetFramework` 的 workaround 命令 + - 当前任务仍属于 PR #288 review follow-up,因此本轮重点改为“区分哪些 AI 建议值得采纳”以及“用真实 WSL build 结果验证” +- 主线程实施: + - 重新抓取 PR #288 review,确认 latest-head open threads 为 `CodeRabbit 6 + Greptile 2` + - 复核 `outside diff + nitpick` 的 19 条建议,只采纳本地仍成立的建议;拒绝把“评论总数”机械等同于“必须全改” + - 完成以下高信号修复: + - `ContextAware*` / `AsyncExtensions` / `NumericExtensions` / `StringExtensions` / `StoreBuilder`:回退为 `ArgumentNullException.ThrowIfNull(...)` + - `ArchitectureServicesTests` / `GameContextTests`:同步 XML `` 到 `NotSupportedException` + - `RegistryInitializationHookBaseTests`:修复 override 可空签名实现,避免再次引入编译错误 + - `RollingFileAppenderTests` / `TaskCoroutineExtensionsTests` / `WaitForTaskTests` / `ScopedStorage`:移除无收益噪音代码 + - `FileStorage`:通过 `leaveOpen: true` 修正 `FileStream` 的双重释放语义 + - `SceneRouterBase`:统一显式 `ConfigureAwait(true)` 并补齐引擎线程亲和说明 + - `StoreSelection`:保留 `net9.0+` 的 `System.Threading.Lock`,同时修正条件编译旁的注释写法,避免 `CS1587` +- 验证里程碑: + - `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal` + - 结果:成功;证明先前 `MSB4018` 来自 stale restore 元数据,而不是当前 WSL 默认 build 路径本身不可用 + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` + - 结果:成功;`28 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release` + - 结果:成功;`329 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:成功;`137 Warning(s)`、`0 Error(s)` +- 当前结论: + - 用户关于“WSL 里直接 `dotnet build` 可行”的判断正确 + - 前一轮失败的核心原因不是仓库不可构建,而是主线程附加的 workaround 参数改变了 MSBuild 行为 + - 本轮已完成 PR #288 中一组仍成立的建议修复,并重新拿到标准 WSL 路径下的 Release build 验证 + - 剩余 review 线程需要在新 head 上重新抓取后再决定是否逐条 resolve + ## 2026-04-25 — RP-063 ### 阶段:先收口 PR #288 latest-head 编译错误,再暂停在环境阻塞点并准备提交