From 8831cb42a8e183a6831a661539ef110a0582aeb0 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Tue, 21 Apr 2026 16:15:36 +0800
Subject: [PATCH 1/3] =?UTF-8?q?fix(core):=20=E7=BB=9F=E4=B8=80=E4=BA=8B?=
=?UTF-8?q?=E4=BB=B6=E7=AD=BE=E5=90=8D=E5=B9=B6=E6=B8=85=E7=90=86MA0046?=
=?UTF-8?q?=E5=91=8A=E8=AD=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
hBc
---
.../ArchitecturePhaseChangedEventArgs.cs | 24 ++++++++
.../AsyncLogFlushCompletedEventArgs.cs | 26 +++++++++
.../ArchitectureLifecycleBehaviorTests.cs | 35 +++++++++++-
.../Architectures/TestArchitectureBase.cs | 4 +-
.../Coroutine/CoroutineSchedulerTests.cs | 57 +++++++++++++++++++
.../Logging/AsyncLogAppenderTests.cs | 28 ++++++++-
GFramework.Core/Architectures/Architecture.cs | 22 +++++--
.../Architectures/ArchitectureLifecycle.cs | 7 +--
.../ArchitecturePhaseCoordinator.cs | 9 +--
.../Coroutine/CoroutineExceptionEventArgs.cs | 29 ++++++++++
.../Coroutine/CoroutineFinishedEventArgs.cs | 42 ++++++++++++++
.../Coroutine/CoroutineScheduler.cs | 8 +--
.../Logging/Appenders/AsyncLogAppender.cs | 9 +--
GFramework.Godot/Coroutine/Timing.cs | 12 ++--
.../analyzer-warning-reduction-tracking.md | 31 +++++++---
.../analyzer-warning-reduction-trace.md | 32 +++++++++++
docs/zh-CN/core/architecture.md | 3 +
docs/zh-CN/core/lifecycle.md | 4 +-
18 files changed, 333 insertions(+), 49 deletions(-)
create mode 100644 GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs
create mode 100644 GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs
create mode 100644 GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs
create mode 100644 GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs
diff --git a/GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs b/GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs
new file mode 100644
index 00000000..2ccdcded
--- /dev/null
+++ b/GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs
@@ -0,0 +1,24 @@
+using GFramework.Core.Abstractions.Enums;
+
+namespace GFramework.Core.Abstractions.Architectures;
+
+///
+/// 表示架构阶段变化事件的数据。
+/// 该类型用于向事件订阅者传递当前已进入的阶段值。
+///
+public sealed class ArchitecturePhaseChangedEventArgs : EventArgs
+{
+ ///
+ /// 初始化 的新实例。
+ ///
+ /// 当前已进入的架构阶段。
+ public ArchitecturePhaseChangedEventArgs(ArchitecturePhase phase)
+ {
+ Phase = phase;
+ }
+
+ ///
+ /// 获取当前已进入的架构阶段。
+ ///
+ public ArchitecturePhase Phase { get; }
+}
diff --git a/GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs b/GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs
new file mode 100644
index 00000000..793b35ac
--- /dev/null
+++ b/GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs
@@ -0,0 +1,26 @@
+namespace GFramework.Core.Abstractions.Logging;
+
+///
+/// 表示异步日志刷新完成事件的数据。
+/// 该类型用于告知订阅者本次刷新是否在超时时间内成功完成。
+///
+public sealed class AsyncLogFlushCompletedEventArgs : EventArgs
+{
+ ///
+ /// 初始化 的新实例。
+ ///
+ ///
+ /// 刷新是否成功完成。
+ /// 为 表示所有待处理日志都已在超时前落地;
+ /// 为 表示刷新超时或输出器已不可用。
+ ///
+ public AsyncLogFlushCompletedEventArgs(bool success)
+ {
+ Success = success;
+ }
+
+ ///
+ /// 获取刷新是否成功完成。
+ ///
+ public bool Success { get; }
+}
diff --git a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs
index 943e2bfd..05f98acc 100644
--- a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs
+++ b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs
@@ -62,6 +62,35 @@ public class ArchitectureLifecycleBehaviorTests
await architecture.DestroyAsync();
}
+ ///
+ /// 验证阶段变更事件会以架构实例作为 sender,并通过事件参数暴露阶段值。
+ ///
+ [Test]
+ public async Task InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs()
+ {
+ var architecture = new PhaseTrackingArchitecture();
+ var observations = new List<(object? Sender, ArchitecturePhase Phase)>();
+
+ architecture.PhaseChanged += (sender, eventArgs) => observations.Add((sender, eventArgs.Phase));
+
+ await architecture.InitializeAsync();
+
+ Assert.That(observations, Is.Not.Empty);
+ Assert.That(observations.All(item => ReferenceEquals(item.Sender, architecture)), Is.True);
+ Assert.That(observations.Select(static item => item.Phase), Is.EqualTo(new[]
+ {
+ ArchitecturePhase.BeforeUtilityInit,
+ ArchitecturePhase.AfterUtilityInit,
+ ArchitecturePhase.BeforeModelInit,
+ ArchitecturePhase.AfterModelInit,
+ ArchitecturePhase.BeforeSystemInit,
+ ArchitecturePhase.AfterSystemInit,
+ ArchitecturePhase.Ready
+ }));
+
+ await architecture.DestroyAsync();
+ }
+
///
/// 验证用户初始化失败时,等待 Ready 的任务会失败并进入 FailedInitialization 阶段。
///
@@ -183,7 +212,7 @@ public class ArchitectureLifecycleBehaviorTests
public PhaseTrackingArchitecture(Action? onInitializeAction = null)
{
_onInitializeAction = onInitializeAction;
- PhaseChanged += phase => PhaseHistory.Add(phase);
+ PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase);
}
///
@@ -214,7 +243,7 @@ public class ArchitectureLifecycleBehaviorTests
public DestroyOrderArchitecture(List destroyOrder)
{
_destroyOrder = destroyOrder;
- PhaseChanged += phase => PhaseHistory.Add(phase);
+ PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase);
}
///
@@ -247,7 +276,7 @@ public class ArchitectureLifecycleBehaviorTests
public FailingInitializationArchitecture(List destroyOrder)
{
_destroyOrder = destroyOrder;
- PhaseChanged += phase => PhaseHistory.Add(phase);
+ PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase);
}
///
diff --git a/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs b/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs
index 90f3d436..57cf9a71 100644
--- a/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs
+++ b/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs
@@ -43,6 +43,6 @@ public abstract class TestArchitectureBase : Architecture
_postRegistrationHook?.Invoke(this);
// 订阅阶段变更事件以记录历史
- PhaseChanged += phase => PhaseHistory.Add(phase);
+ PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase);
}
-}
\ No newline at end of file
+}
diff --git a/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs b/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs
index b14833ad..432eddf8 100644
--- a/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs
+++ b/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs
@@ -331,6 +331,63 @@ public class CoroutineSchedulerTests
Assert.That(_scheduler.ActiveCoroutineCount, Is.EqualTo(0));
}
+ ///
+ /// 验证完成事件会把调度器实例、句柄和完成结果暴露给订阅者。
+ ///
+ [Test]
+ public void Run_Should_Raise_OnCoroutineFinished_With_EventArgs()
+ {
+ object? observedSender = null;
+ CoroutineFinishedEventArgs? observedArgs = null;
+
+ _scheduler.OnCoroutineFinished += (sender, eventArgs) =>
+ {
+ observedSender = sender;
+ observedArgs = eventArgs;
+ };
+
+ var handle = _scheduler.Run(CreateSimpleCoroutine());
+
+ _scheduler.Update();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(observedSender, Is.SameAs(_scheduler));
+ Assert.That(observedArgs, Is.Not.Null);
+ Assert.That(observedArgs!.Handle, Is.EqualTo(handle));
+ Assert.That(observedArgs.CompletionStatus, Is.EqualTo(CoroutineCompletionStatus.Completed));
+ Assert.That(observedArgs.Exception, Is.Null);
+ });
+ }
+
+ ///
+ /// 验证异常事件会把调度器实例、失败句柄和异常对象暴露给订阅者。
+ ///
+ [Test]
+ public async Task Scheduler_Should_Raise_OnCoroutineException_With_EventArgs()
+ {
+ var exceptionSource =
+ new TaskCompletionSource<(object? Sender, CoroutineExceptionEventArgs EventArgs)>(
+ TaskCreationOptions.RunContinuationsAsynchronously);
+ _scheduler.OnCoroutineException += (sender, eventArgs) =>
+ {
+ exceptionSource.TrySetResult((sender, eventArgs));
+ };
+
+ var handle = _scheduler.Run(CreateExceptionCoroutine());
+
+ _scheduler.Update();
+ var observation = await exceptionSource.Task;
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(observation.Sender, Is.SameAs(_scheduler));
+ Assert.That(observation.EventArgs.Handle, Is.EqualTo(handle));
+ Assert.That(observation.EventArgs.Exception, Is.TypeOf());
+ Assert.That(observation.EventArgs.Exception.Message, Is.EqualTo("Test exception"));
+ });
+ }
+
///
/// 验证协程调度器应该扩展容量当槽位已满
///
diff --git a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
index ae1537ca..cc7a0985 100644
--- a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
+++ b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
@@ -77,6 +77,32 @@ public class AsyncLogAppenderTests
Assert.That(innerAppender.Entries.Count, Is.EqualTo(100));
}
+ [Test]
+ public void Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result()
+ {
+ var innerAppender = new TestAppender();
+ using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 10);
+ object? observedSender = null;
+ AsyncLogFlushCompletedEventArgs? observedArgs = null;
+
+ asyncAppender.Append(new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Flush check", null, null));
+
+ asyncAppender.OnFlushCompleted += (sender, eventArgs) =>
+ {
+ observedSender = sender;
+ observedArgs = eventArgs;
+ };
+
+ var result = asyncAppender.Flush(TimeSpan.FromSeconds(1));
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(observedSender, Is.SameAs(asyncAppender));
+ Assert.That(observedArgs, Is.Not.Null);
+ Assert.That(observedArgs!.Success, Is.EqualTo(result));
+ });
+ }
+
[Test]
public void Dispose_ShouldProcessRemainingEntries()
{
@@ -296,4 +322,4 @@ public class AsyncLogAppenderTests
{
}
}
-}
\ No newline at end of file
+}
diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs
index b9e2ecf6..6a284145 100644
--- a/GFramework.Core/Architectures/Architecture.cs
+++ b/GFramework.Core/Architectures/Architecture.cs
@@ -49,6 +49,7 @@ public abstract class Architecture : IArchitecture
// 初始化管理器
_bootstrapper = new ArchitectureBootstrapper(GetType(), resolvedEnvironment, resolvedServices, _logger);
_lifecycle = new ArchitectureLifecycle(this, resolvedConfiguration, resolvedServices, _logger);
+ _lifecycle.PhaseChanged += HandleLifecyclePhaseChanged;
_componentRegistry = new ArchitectureComponentRegistry(
this,
resolvedConfiguration,
@@ -100,11 +101,7 @@ public abstract class Architecture : IArchitecture
///
/// 阶段变更事件(用于测试和扩展)
///
- public event Action? PhaseChanged
- {
- add => _lifecycle.PhaseChanged += value;
- remove => _lifecycle.PhaseChanged -= value;
- }
+ public event EventHandler? PhaseChanged;
#endregion
@@ -142,6 +139,21 @@ public abstract class Architecture : IArchitecture
#endregion
+ #region Event Relays
+
+ ///
+ /// 把生命周期协作者的阶段广播重新映射到当前架构实例,
+ /// 以便公开事件的 sender 始终反映真实的架构发布者。
+ ///
+ /// 生命周期协作者实例。
+ /// 阶段变化事件数据。
+ private void HandleLifecyclePhaseChanged(object? sender, ArchitecturePhaseChangedEventArgs eventArgs)
+ {
+ PhaseChanged?.Invoke(this, eventArgs);
+ }
+
+ #endregion
+
#region Module Management
///
diff --git a/GFramework.Core/Architectures/ArchitectureLifecycle.cs b/GFramework.Core/Architectures/ArchitectureLifecycle.cs
index c0fed21f..f70d5fe9 100644
--- a/GFramework.Core/Architectures/ArchitectureLifecycle.cs
+++ b/GFramework.Core/Architectures/ArchitectureLifecycle.cs
@@ -71,6 +71,7 @@ internal sealed class ArchitectureLifecycle(
public void EnterPhase(ArchitecturePhase next)
{
_phaseCoordinator.EnterPhase(next);
+ PhaseChanged?.Invoke(this, new ArchitecturePhaseChangedEventArgs(next));
}
#endregion
@@ -127,11 +128,7 @@ internal sealed class ArchitectureLifecycle(
///
/// 阶段变更事件(用于测试和扩展)
///
- public event Action? PhaseChanged
- {
- add => _phaseCoordinator.PhaseChanged += value;
- remove => _phaseCoordinator.PhaseChanged -= value;
- }
+ public event EventHandler? PhaseChanged;
#endregion
diff --git a/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs b/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs
index e1fc5f9d..51675ac9 100644
--- a/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs
+++ b/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs
@@ -22,12 +22,6 @@ internal sealed class ArchitecturePhaseCoordinator(
///
public ArchitecturePhase CurrentPhase { get; private set; }
- ///
- /// 在架构阶段变更时触发。
- /// 该事件用于测试和扩展场景,保持现有公共行为不变。
- ///
- public event Action? PhaseChanged;
-
///
/// 注册一个生命周期钩子。
/// 就绪后是否允许追加注册由架构配置控制,以保证阶段回调的一致性。
@@ -61,7 +55,6 @@ internal sealed class ArchitecturePhaseCoordinator(
NotifyLifecycleHooks(next);
NotifyPhaseListeners(next);
- PhaseChanged?.Invoke(next);
}
///
@@ -113,4 +106,4 @@ internal sealed class ArchitecturePhaseCoordinator(
listener.OnArchitecturePhase(phase);
}
}
-}
\ No newline at end of file
+}
diff --git a/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs b/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs
new file mode 100644
index 00000000..cddfc054
--- /dev/null
+++ b/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs
@@ -0,0 +1,29 @@
+namespace GFramework.Core.Coroutine;
+
+///
+/// 表示协程异常事件的数据。
+/// 该类型用于把失败协程的句柄与实际异常一起传递给订阅者。
+///
+public sealed class CoroutineExceptionEventArgs : EventArgs
+{
+ ///
+ /// 初始化 的新实例。
+ ///
+ /// 发生异常的协程句柄。
+ /// 协程执行过程中抛出的异常。
+ public CoroutineExceptionEventArgs(CoroutineHandle handle, Exception exception)
+ {
+ Handle = handle;
+ Exception = exception ?? throw new ArgumentNullException(nameof(exception));
+ }
+
+ ///
+ /// 获取发生异常的协程句柄。
+ ///
+ public CoroutineHandle Handle { get; }
+
+ ///
+ /// 获取协程执行过程中抛出的异常。
+ ///
+ public Exception Exception { get; }
+}
diff --git a/GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs b/GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs
new file mode 100644
index 00000000..5a32ff9f
--- /dev/null
+++ b/GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs
@@ -0,0 +1,42 @@
+using GFramework.Core.Abstractions.Coroutine;
+
+namespace GFramework.Core.Coroutine;
+
+///
+/// 表示协程结束事件的数据。
+/// 该类型统一描述协程完成、取消或失败后的最终结果。
+///
+public sealed class CoroutineFinishedEventArgs : EventArgs
+{
+ ///
+ /// 初始化 的新实例。
+ ///
+ /// 已结束的协程句柄。
+ /// 协程最终结果。
+ /// 若协程以失败结束,则为对应异常;否则为 。
+ public CoroutineFinishedEventArgs(
+ CoroutineHandle handle,
+ CoroutineCompletionStatus completionStatus,
+ Exception? exception)
+ {
+ Handle = handle;
+ CompletionStatus = completionStatus;
+ Exception = exception;
+ }
+
+ ///
+ /// 获取已结束的协程句柄。
+ ///
+ public CoroutineHandle Handle { get; }
+
+ ///
+ /// 获取协程最终结果。
+ ///
+ public CoroutineCompletionStatus CompletionStatus { get; }
+
+ ///
+ /// 获取协程失败时对应的异常对象。
+ /// 对于完成或取消结果,该值为 。
+ ///
+ public Exception? Exception { get; }
+}
diff --git a/GFramework.Core/Coroutine/CoroutineScheduler.cs b/GFramework.Core/Coroutine/CoroutineScheduler.cs
index ece62578..ed57b06f 100644
--- a/GFramework.Core/Coroutine/CoroutineScheduler.cs
+++ b/GFramework.Core/Coroutine/CoroutineScheduler.cs
@@ -91,7 +91,7 @@ public sealed class CoroutineScheduler(
/// 为了避免阻塞调度器主循环,该事件会被派发到线程池回调中执行。
/// 如果调用方需要与宿主线程保持一致,请同时订阅 。
///
- public event Action? OnCoroutineException;
+ public event EventHandler? OnCoroutineException;
///
/// 当协程以完成、取消或失败任一结果结束时触发。
@@ -99,7 +99,7 @@ public sealed class CoroutineScheduler(
///
/// 该事件在调度器所在的驱动线程中同步触发,适合与宿主生命周期管理逻辑集成。
///
- public event Action? OnCoroutineFinished;
+ public event EventHandler? OnCoroutineFinished;
///
/// 检查指定协程句柄是否仍然处于活跃状态。
@@ -622,7 +622,7 @@ public sealed class CoroutineScheduler(
UpdateCompletionMetadata(handle, completionStatus);
ReleaseCompletedCoroutine(slotIndex, slot, handle);
CompleteCoroutineLifecycle(handle, completionStatus);
- OnCoroutineFinished?.Invoke(handle, completionStatus, exception);
+ OnCoroutineFinished?.Invoke(this, new CoroutineFinishedEventArgs(handle, completionStatus, exception));
}
///
@@ -642,7 +642,7 @@ public sealed class CoroutineScheduler(
{
try
{
- handler(handle, ex);
+ handler(this, new CoroutineExceptionEventArgs(handle, ex));
}
catch (Exception callbackEx)
{
diff --git a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
index 0a906f46..eda3f9a3 100644
--- a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
+++ b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
@@ -118,13 +118,14 @@ public sealed class AsyncLogAppender : ILogAppender
void ILogAppender.Flush()
{
var success = Flush();
- OnFlushCompleted?.Invoke(success);
+ OnFlushCompleted?.Invoke(this, new AsyncLogFlushCompletedEventArgs(success));
}
///
- /// Flush 操作完成事件,参数指示是否成功(true)或超时(false)
+ /// Flush 操作完成事件。
+ /// 事件数据通过 提供。
///
- public event Action? OnFlushCompleted;
+ public event EventHandler? OnFlushCompleted;
///
/// 刷新缓冲区,等待所有日志写入完成
@@ -145,7 +146,7 @@ public sealed class AsyncLogAppender : ILogAppender
{
// 等待处理任务发出完成信号
var success = _flushSemaphore.Wait(actualTimeout);
- OnFlushCompleted?.Invoke(success);
+ OnFlushCompleted?.Invoke(this, new AsyncLogFlushCompletedEventArgs(success));
return success;
}
finally
diff --git a/GFramework.Godot/Coroutine/Timing.cs b/GFramework.Godot/Coroutine/Timing.cs
index a2094ffc..2bab42a2 100644
--- a/GFramework.Godot/Coroutine/Timing.cs
+++ b/GFramework.Godot/Coroutine/Timing.cs
@@ -781,15 +781,13 @@ public partial class Timing : Node
///
/// 在协程结束时解除节点归属回调并清理索引。
///
- /// 已结束的协程句柄。
- /// 协程最终状态。
- /// 若失败则为异常对象。
+ /// 触发事件的协程调度器。
+ /// 协程结束事件数据。
private void HandleCoroutineFinished(
- CoroutineHandle handle,
- CoroutineCompletionStatus status,
- Exception? exception)
+ object? sender,
+ CoroutineFinishedEventArgs eventArgs)
{
- CleanupOwnedCoroutineRegistration(handle);
+ CleanupOwnedCoroutineRegistration(eventArgs.Handle);
}
///
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 53f3204b..e84369d9 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
@@ -7,10 +7,12 @@
## 当前恢复点
-- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-012`
-- 当前阶段:`Phase 12`
+- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-013`
+- 当前阶段:`Phase 13`
- 当前焦点:
- - 当前 PR review workflow 已补强到支持 JSON 落盘与按 section/path 收窄输出;下一轮恢复到 `MA0046` 主批次
+ - 当前 `MA0046` 主批次已在 `Architecture`、`AsyncLogAppender` 与 `CoroutineScheduler` 上收口完成
+ - 下一轮优先从 `MA0016` 或 `MA0002` 中选择低风险批次继续推进;`MA0015` 与 `MA0077` 继续作为尾项顺手吸收
+ - `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
- 当某一轮主类型数量不足时,允许顺手合并其他低冲突 warning 类型,`MA0015` 与 `MA0077`
只是当前最明显的低数量示例,不构成限定
@@ -24,8 +26,9 @@
- 已完成当前 PR #265 review follow-up:修复 `CoroutineScheduler` 的零容量扩容边界,并补上 `Store` dispatch 作用域的异常安全回滚
- 已继续完成当前 PR #265 review follow-up:修复 `Event` 与 `Event` 监听器计数的 off-by-one,并补充回归测试
- 已增强 `gframework-pr-review` 脚本与 skill 文档,降低超长 JSON 直出导致的 review 信号漏看风险
-- 当前 `PauseStackManager`、`Store`、`CoroutineScheduler` 与 `GFramework.Core` 的 `MA0048`
- 文件/类型命名冲突已从 active 入口移除;主题内剩余 warning 主要集中在 `MA0046` delegate 形状、
+- 已完成 `GFramework.Core` 当前 `MA0046` 批次:将阶段、协程与异步日志事件统一迁移到 `EventHandler` 形状,
+ 并同步更新 `GFramework.Godot` 订阅点、定向测试与 `docs/zh-CN` 示例
+- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `9` 条;剩余 warning 集中在
`MA0016` 集合抽象接口、`MA0002` comparer 重载,以及 `MA0015` / `MA0077` 两个低数量尾项
## 当前活跃事实
@@ -51,16 +54,20 @@
委托导致的 `GetListenerCount()` off-by-one,并以定向事件测试验证注册、注销和计数语义
- `RP-012` 为 `gframework-pr-review` 增加 `--json-output`、`--section`、`--path` 与文本截断能力,并更新 skill 推荐用法,
让“先落盘、再定向抽取”成为默认可操作路径
+- `RP-013` 已完成 `GFramework.Core` 当前 `MA0046` 批次,并以新的事件参数类型替换阶段、协程和异步日志事件的
+ 非标准签名;`GFramework.Core` `net8.0` warnings-only 基线由 `15` 降至 `9`
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
## 当前风险
-- 公共契约兼容风险:剩余 `MA0046` / `MA0016` 若直接改公开委托或集合类型,可能波及用户代码
+- 公共契约兼容风险:剩余 `MA0016` 若直接改公开集合类型,可能波及用户代码
- 缓解措施:优先选择不改公共 API 的低风险切法;若必须触达公共契约,先补齐 XML 契约说明与定向测试
- 测试宿主稳定性风险:部分 Godot 失败路径在当前 .NET 测试宿主下仍不稳定
- 缓解措施:继续优先使用稳定的 targeted test、项目构建和相邻 smoke test 组合验证
- 多目标框架 warning 解释风险:同一源位置会在多个 target framework 下重复计数
- 缓解措施:继续以唯一源位置和 warning 家族为主要决策依据,而不是只看原始 warning 总数
+- Godot 资产文件环境风险:当前 worktree 的 `GFramework.Godot` restore/build 仍会命中 Windows fallback package folder
+ - 缓解措施:后续若继续触达 Godot 模块,先用 Linux 侧 restore 资产或 Windows-hosted 构建链刷新该项目,再补跑定向 build
- 并行实现风险:批量收敛时若 subagent 写入边界不清晰,容易引入命名冲突或重复重构
- 缓解措施:只在 warning 类型或目录边界清晰时并行;每个 subagent 必须有独占文件 ownership,主代理负责合并验证
@@ -121,11 +128,19 @@
- 结果:通过;`--json-output`、`--section`、`--path`、`--max-description-length` 已出现在 CLI 帮助中
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
- 结果:`0 Warning(s)`,`0 Error(s)`
+- `RP-013` 的定向验证结果:
+ - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
+ - 结果:`9 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` warnings-only 输出中已不再出现 `MA0046`
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
+ - 结果:`50 Passed`,`0 Failed`
+ - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo`
+ - 结果:失败;当前 worktree 的 Godot restore 资产仍引用 Windows fallback package folder,尚未完成独立项目编译验证
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步
1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录
-2. 下一轮优先以 `MA0046` 为主批次启动,先从 `Architecture*` 与 `CoroutineScheduler` 的低风险 delegate 形状修正中选一个切入点
-3. 若 `MA0046` 的文件 ownership 可以清晰切分,允许使用不同模型的 subagent 并行处理互不冲突的目录或类型簇
+2. 下一轮优先在 `MA0016` 与 `MA0002` 之间选择低风险批次继续推进,默认先看 `LoggingConfiguration` /
+ `FilterConfiguration` 与 `CollectionExtensions`
+3. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build
4. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/`
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 d0f53094..666ab845 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,37 @@
# Analyzer Warning Reduction 追踪
+## 2026-04-21 — RP-013
+
+### 阶段:`MA0046` 事件签名批次收口(RP-013)
+
+- 依据 `RP-012` 的下一步建议,本轮恢复到 `GFramework.Core` 的 `MA0046` 主批次,而不是继续停留在 PR review workflow 优化
+- 本地 warnings-only 基线确认当前 `GFramework.Core` `net8.0` 仍有 `6` 个 `MA0046`:
+ - `Architecture.cs`
+ - `ArchitectureLifecycle.cs`
+ - `ArchitecturePhaseCoordinator.cs`
+ - `AsyncLogAppender.cs`
+ - `CoroutineScheduler.cs` 两处事件
+- 方案选择:
+ - 不再保留 `Action<...>` 事件签名,统一改为标准 `EventHandler`
+ - 为 `Architecture`、`AsyncLogAppender` 新增放在 `GFramework.Core.Abstractions` 的事件参数类型
+ - 为 `CoroutineScheduler` 新增放在 `GFramework.Core` 的事件参数类型,因为 `CoroutineHandle` 定义在 runtime 层,不适合反向放入 Abstractions
+ - `Architecture` 相关事件采用 `Coordinator -> Lifecycle -> Architecture` relay,而不是直接透传底层事件,确保公开事件的 sender 始终是实际发布者,并避免引入新的 `MA0091`
+- 同步适配:
+ - 更新 `GFramework.Godot/Coroutine/Timing.cs` 的 `OnCoroutineFinished` 订阅签名
+ - 更新 `ArchitectureLifecycleBehaviorTests`、`CoroutineSchedulerTests`、`AsyncLogAppenderTests` 以覆盖 sender / event args 契约
+ - 更新 `docs/zh-CN/core/architecture.md` 与 `docs/zh-CN/core/lifecycle.md` 的 `PhaseChanged` 示例
+- 验证结果:
+ - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
+ - 结果:`9 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` 输出中已无 `MA0046`
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
+ - 结果:`50 Passed`,`0 Failed`
+ - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo`
+ - 结果:失败;当前 worktree 的 `project.assets.json` 仍引用 Windows fallback package folder,尚未完成 Godot 独立编译验证
+- 当前结论:
+ - `MA0046` 已从 active 批次中移除
+ - 剩余 `GFramework.Core` `net8.0` warning 分布更新为:`MA0016=5`、`MA0002=2`、`MA0015=1`、`MA0077=1`
+ - 若继续本主题,下一步默认转入 `MA0016` 批次;若继续触达 Godot,再先修复该项目 restore 资产
+
## 2026-04-21 — RP-012
### 阶段:PR review workflow 输出收窄增强(RP-012)
diff --git a/docs/zh-CN/core/architecture.md b/docs/zh-CN/core/architecture.md
index e1900382..3c54e1bb 100644
--- a/docs/zh-CN/core/architecture.md
+++ b/docs/zh-CN/core/architecture.md
@@ -123,6 +123,9 @@ protected override void OnInitialize()
- `PhaseChanged`
- `RegisterLifecycleHook(...)`
+其中 `PhaseChanged` 现在遵循标准 `EventHandler` 约定,
+阶段值通过 `args.Phase` 读取。
+
如果你需要在 `Ready`、`Destroying` 等阶段执行横切逻辑,比起把这类逻辑塞进某个具体 `System`,更适合单独实现
`IArchitectureLifecycleHook`。
diff --git a/docs/zh-CN/core/lifecycle.md b/docs/zh-CN/core/lifecycle.md
index a546070d..4cdaa8fc 100644
--- a/docs/zh-CN/core/lifecycle.md
+++ b/docs/zh-CN/core/lifecycle.md
@@ -139,9 +139,9 @@ architecture.RegisterLifecycleHook(new MetricsHook());
如果你只需要观察阶段变化,也可以直接订阅:
```csharp
-architecture.PhaseChanged += phase =>
+architecture.PhaseChanged += (_, args) =>
{
- Console.WriteLine($"Phase changed: {phase}");
+ Console.WriteLine($"Phase changed: {args.Phase}");
};
```
From 685897f2de66fa05e3b9ce11cc45571bd8e6a487 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Tue, 21 Apr 2026 16:50:56 +0800
Subject: [PATCH 2/3] =?UTF-8?q?fix(core):=20=E6=94=B6=E5=8F=A3=20PR267=20?=
=?UTF-8?q?=E4=BA=8B=E4=BB=B6=E5=A5=91=E7=BA=A6=E9=81=97=E7=95=99=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 AsyncLogAppender 接口刷新路径重复触发完成事件,并补充单次通知回归测试
- 补充 Architecture、CoroutineExceptionEventArgs 与阶段协调器的事件契约注释
- 更新 PhaseChanged 迁移文档与 analyzer-warning-reduction recovery 记录
---
.../Coroutine/CoroutineSchedulerTests.cs | 2 +-
.../Logging/AsyncLogAppenderTests.cs | 17 ++++++++++
GFramework.Core/Architectures/Architecture.cs | 10 +++++-
.../ArchitecturePhaseCoordinator.cs | 4 +--
.../Coroutine/CoroutineExceptionEventArgs.cs | 1 +
.../Logging/Appenders/AsyncLogAppender.cs | 3 +-
.../analyzer-warning-reduction-tracking.md | 24 +++++++++++---
.../analyzer-warning-reduction-trace.md | 32 +++++++++++++++++++
docs/zh-CN/core/architecture.md | 11 +++++++
docs/zh-CN/core/lifecycle.md | 3 ++
10 files changed, 96 insertions(+), 11 deletions(-)
diff --git a/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs b/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs
index 432eddf8..08b0fb8a 100644
--- a/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs
+++ b/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs
@@ -377,7 +377,7 @@ public class CoroutineSchedulerTests
var handle = _scheduler.Run(CreateExceptionCoroutine());
_scheduler.Update();
- var observation = await exceptionSource.Task;
+ var observation = await exceptionSource.Task.WaitAsync(TimeSpan.FromSeconds(3));
Assert.Multiple(() =>
{
diff --git a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
index cc7a0985..89db303c 100644
--- a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
+++ b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
@@ -103,6 +103,23 @@ public class AsyncLogAppenderTests
});
}
+ [Test]
+ public void ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once()
+ {
+ var innerAppender = new TestAppender();
+ using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 10);
+ ILogAppender logAppender = asyncAppender;
+ var observedResults = new List();
+
+ asyncAppender.Append(new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Interface flush check", null, null));
+ asyncAppender.OnFlushCompleted += (_, eventArgs) => observedResults.Add(eventArgs.Success);
+
+ logAppender.Flush();
+
+ Assert.That(observedResults, Has.Count.EqualTo(1));
+ Assert.That(observedResults, Has.All.True);
+ }
+
[Test]
public void Dispose_ShouldProcessRemainingEntries()
{
diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs
index 6a284145..509fd94a 100644
--- a/GFramework.Core/Architectures/Architecture.cs
+++ b/GFramework.Core/Architectures/Architecture.cs
@@ -99,8 +99,16 @@ public abstract class Architecture : IArchitecture
public virtual Action? Configurator => null;
///
- /// 阶段变更事件(用于测试和扩展)
+ /// 在架构生命周期阶段发生变化时触发。
///
+ ///
+ ///
+ /// 订阅者应通过 读取当前阶段,而不是依赖内部生命周期对象。
+ ///
+ ///
+ /// 事件委托中的 sender 始终为当前 实例,便于测试与外部扩展保持稳定的发布者契约。
+ ///
+ ///
public event EventHandler? PhaseChanged;
#endregion
diff --git a/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs b/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs
index 51675ac9..42cf9578 100644
--- a/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs
+++ b/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs
@@ -39,8 +39,8 @@ internal sealed class ArchitecturePhaseCoordinator(
///
/// 进入指定阶段并广播给所有阶段消费者。
- /// 顺序保持为“更新阶段值 → 生命周期钩子 → 容器中的阶段监听器 → 外部事件”,
- /// 以兼容既有调用约定。
+ /// 顺序保持为“更新阶段值 → 生命周期钩子 → 容器中的阶段监听器”,
+ /// 以保证框架扩展与运行时组件看到一致的阶段视图。
///
/// 目标阶段。
public void EnterPhase(ArchitecturePhase next)
diff --git a/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs b/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs
index cddfc054..dfacd69c 100644
--- a/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs
+++ b/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs
@@ -11,6 +11,7 @@ public sealed class CoroutineExceptionEventArgs : EventArgs
///
/// 发生异常的协程句柄。
/// 协程执行过程中抛出的异常。
+ /// 为 。
public CoroutineExceptionEventArgs(CoroutineHandle handle, Exception exception)
{
Handle = handle;
diff --git a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
index eda3f9a3..c377acce 100644
--- a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
+++ b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
@@ -117,8 +117,7 @@ public sealed class AsyncLogAppender : ILogAppender
///
void ILogAppender.Flush()
{
- var success = Flush();
- OnFlushCompleted?.Invoke(this, new AsyncLogFlushCompletedEventArgs(success));
+ Flush();
}
///
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 e84369d9..2c4d2b08 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
@@ -7,11 +7,13 @@
## 当前恢复点
-- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-013`
-- 当前阶段:`Phase 13`
+- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-014`
+- 当前阶段:`Phase 14`
- 当前焦点:
- - 当前 `MA0046` 主批次已在 `Architecture`、`AsyncLogAppender` 与 `CoroutineScheduler` 上收口完成
- - 下一轮优先从 `MA0016` 或 `MA0002` 中选择低风险批次继续推进;`MA0015` 与 `MA0077` 继续作为尾项顺手吸收
+ - 当前分支 PR #267 的 latest CodeRabbit unresolved threads、outside-diff comment 与 nitpick comment 已完成本地复核
+ - 本轮优先收口仍然成立的 follow-up:`AsyncLogAppender` 接口路径重复触发 `OnFlushCompleted`、事件/XML 契约缺口、
+ `PhaseChanged` 迁移文档说明与 `ai-plan` 基线表述歧义
+ - 下一轮默认恢复到 `MA0016` 或 `MA0002` 低风险批次;`MA0015` 与 `MA0077` 继续作为尾项顺手吸收
- `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
- 当某一轮主类型数量不足时,允许顺手合并其他低冲突 warning 类型,`MA0015` 与 `MA0077`
@@ -28,6 +30,8 @@
- 已增强 `gframework-pr-review` 脚本与 skill 文档,降低超长 JSON 直出导致的 review 信号漏看风险
- 已完成 `GFramework.Core` 当前 `MA0046` 批次:将阶段、协程与异步日志事件统一迁移到 `EventHandler` 形状,
并同步更新 `GFramework.Godot` 订阅点、定向测试与 `docs/zh-CN` 示例
+- 已完成当前 PR #267 review follow-up:修复 `AsyncLogAppender` 的 `ILogAppender.Flush()` 双重完成通知,并补齐
+ `PhaseChanged` / `CoroutineExceptionEventArgs` XML 文档、`PhaseChanged` 迁移说明和 `ai-plan` 基线注释
- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `9` 条;剩余 warning 集中在
`MA0016` 集合抽象接口、`MA0002` comparer 重载,以及 `MA0015` / `MA0077` 两个低数量尾项
@@ -56,6 +60,8 @@
让“先落盘、再定向抽取”成为默认可操作路径
- `RP-013` 已完成 `GFramework.Core` 当前 `MA0046` 批次,并以新的事件参数类型替换阶段、协程和异步日志事件的
非标准签名;`GFramework.Core` `net8.0` warnings-only 基线由 `15` 降至 `9`
+- `RP-014` 使用 `gframework-pr-review` 复核当前分支 PR #267 的 latest head review threads、outside-diff comment 与
+ nitpick comment 后,确认 8 条高信号项中仍成立的是 1 个行为 bug 与 7 个文档/测试/跟踪缺口,并按最小改动收口
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
## 当前风险
@@ -130,11 +136,19 @@
- 结果:`0 Warning(s)`,`0 Error(s)`
- `RP-013` 的定向验证结果:
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
- - 结果:`9 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` warnings-only 输出中已不再出现 `MA0046`
+ - 结果:`9 Warning(s)`,`0 Error(s)`;相对 `RP-009` / `RP-011` 的 warnings-only 基线 `15 Warning(s)` 已降到 `9 Warning(s)`,
+ 当前 `GFramework.Core` `net8.0` 输出中已不再出现 `MA0046`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`50 Passed`,`0 Failed`
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo`
- 结果:失败;当前 worktree 的 Godot restore 资产仍引用 Windows fallback package folder,尚未完成独立项目编译验证
+- `RP-014` 的定向验证结果:
+ - `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
+ - 结果:通过;host Windows `dotnet` 首次验证前补齐了缺失的 `Meziantou.Analyzer 3.0.48` 包
+ - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
+ - 结果:`9 Warning(s)`,`0 Error(s)`;`AsyncLogAppender` 行为修复与 XML / 文档补充未引入新的 `GFramework.Core` `net8.0` 构建错误
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CoroutineSchedulerTests.Scheduler_Should_Raise_OnCoroutineException_With_EventArgs|FullyQualifiedName~AsyncLogAppenderTests.Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result|FullyQualifiedName~AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once|FullyQualifiedName~ArchitectureLifecycleBehaviorTests.InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs" -m:1 -p:RestoreFallbackFolders="" -nologo`
+ - 结果:`4 Passed`,`0 Failed`
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步
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 666ab845..369c76ce 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,37 @@
# Analyzer Warning Reduction 追踪
+## 2026-04-21 — RP-014
+
+### 阶段:PR #267 review follow-up 收口(RP-014)
+
+- 使用 `gframework-pr-review` 抓取当前分支 PR #267 的 latest head review threads、outside-diff comment、nitpick comment、
+ MegaLinter 摘要与测试报告,并确认本轮除了 6 条 open thread 之外,还存在 1 条 outside-diff 与 1 条 nitpick 需要一并复核
+- 本地复核后确认仍成立的项:
+ - `AsyncLogAppender` 的显式接口实现 `ILogAppender.Flush()` 会在调用 `Flush()` 后再次手动触发 `OnFlushCompleted`,
+ 导致接口路径重复通知
+ - `Architecture.PhaseChanged`、`CoroutineExceptionEventArgs` 与 `ArchitecturePhaseCoordinator.EnterPhase` 的 XML/注释契约仍未完全同步
+ - `CoroutineSchedulerTests` 的异常事件测试缺少测试级超时
+ - `docs/zh-CN/core/architecture.md` 与 `docs/zh-CN/core/lifecycle.md` 仍缺少明确的 `PhaseChanged` 迁移说明
+ - `ai-plan` active tracking 中 `RP-013` 的 `9 Warning(s)` 需要明确是相对 `RP-009` / `RP-011` 的 warnings-only 基线收敛
+- 实施最小修复:
+ - 删除 `ILogAppender.Flush()` 中重复的完成事件触发,只保留 `Flush(TimeSpan?)` 内的单一通知源
+ - 为接口调用路径补充单次完成通知回归测试,并为协程异常事件测试增加 `WaitAsync(TimeSpan.FromSeconds(3))`
+ - 补齐 `Architecture.PhaseChanged`、`CoroutineExceptionEventArgs` 与 `ArchitecturePhaseCoordinator.EnterPhase` 的契约文档
+ - 在 `docs/zh-CN/core/architecture.md` 与 `docs/zh-CN/core/lifecycle.md` 中加入 `phase => ...` 迁移到 `(_, args) => ...` 的说明
+ - 更新 `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md` 的恢复点、基线描述与验证结果
+- 验证结果:
+ - `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
+ - 结果:通过;host Windows `dotnet` 首次验证前补齐了缺失的 `Meziantou.Analyzer 3.0.48`
+ - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
+ - 结果:`9 Warning(s)`,`0 Error(s)`
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CoroutineSchedulerTests.Scheduler_Should_Raise_OnCoroutineException_With_EventArgs|FullyQualifiedName~AsyncLogAppenderTests.Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result|FullyQualifiedName~AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once|FullyQualifiedName~ArchitectureLifecycleBehaviorTests.InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs" -m:1 -p:RestoreFallbackFolders="" -nologo`
+ - 结果:`4 Passed`,`0 Failed`
+- 当前结论:
+ - PR #267 里当前仍成立的 CodeRabbit 高信号项已在本地收口
+ - 修复内容没有改变 `EventHandler` 迁移方向,只是补齐行为、文档与恢复信息
+- 下一步建议:
+ - 恢复到 `MA0016` / `MA0002` 主批次,默认先看 `LoggingConfiguration`、`FilterConfiguration` 与 `CollectionExtensions`
+
## 2026-04-21 — RP-013
### 阶段:`MA0046` 事件签名批次收口(RP-013)
diff --git a/docs/zh-CN/core/architecture.md b/docs/zh-CN/core/architecture.md
index 3c54e1bb..9744a844 100644
--- a/docs/zh-CN/core/architecture.md
+++ b/docs/zh-CN/core/architecture.md
@@ -126,10 +126,21 @@ protected override void OnInitialize()
其中 `PhaseChanged` 现在遵循标准 `EventHandler` 约定,
阶段值通过 `args.Phase` 读取。
+如果你正在从旧版本迁移,需要把单参数写法 `phase => ...` 改成 `(_, args) => ...`,
+并通过 `ArchitecturePhaseChangedEventArgs.Phase` 读取阶段值。
+
如果你需要在 `Ready`、`Destroying` 等阶段执行横切逻辑,比起把这类逻辑塞进某个具体 `System`,更适合单独实现
`IArchitectureLifecycleHook`。
```csharp
+architecture.PhaseChanged += (_, args) =>
+{
+ if (args.Phase == ArchitecturePhase.Ready)
+ {
+ Console.WriteLine("Architecture ready from event.");
+ }
+};
+
public sealed class MetricsHook : IArchitectureLifecycleHook
{
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
diff --git a/docs/zh-CN/core/lifecycle.md b/docs/zh-CN/core/lifecycle.md
index 4cdaa8fc..f0f4f096 100644
--- a/docs/zh-CN/core/lifecycle.md
+++ b/docs/zh-CN/core/lifecycle.md
@@ -138,6 +138,9 @@ architecture.RegisterLifecycleHook(new MetricsHook());
如果你只需要观察阶段变化,也可以直接订阅:
+如果你从旧版本的 `PhaseChanged` 迁移过来,需要把旧写法 `phase => ...` 改成 `(_, args) => ...`,
+并通过 `ArchitecturePhaseChangedEventArgs.Phase` 读取阶段值。
+
```csharp
architecture.PhaseChanged += (_, args) =>
{
From a9f86348ff9d2118df0d5c1f859e73beb5459fe1 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Tue, 21 Apr 2026 17:32:50 +0800
Subject: [PATCH 3/3] =?UTF-8?q?fix(core):=20=E4=BF=AE=E5=A4=8D=20AsyncLogA?=
=?UTF-8?q?ppender=20=E5=88=B7=E6=96=B0=E7=AB=9E=E6=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 AsyncLogAppender 在队列已被后台线程提前清空时 Flush 仍可能超时失败的问题
- 新增 AsyncLogAppender 已处理队列场景的稳定回归测试并重新验证 GFramework.Core.Tests
- 更新 analyzer-warning-reduction 的 tracking 与 trace 记录 PR267 failed-test follow-up
---
.../Logging/AsyncLogAppenderTests.cs | 50 +++++++++++++++++++
.../Logging/Appenders/AsyncLogAppender.cs | 40 +++++++++++----
.../analyzer-warning-reduction-tracking.md | 19 +++++--
.../analyzer-warning-reduction-trace.md | 35 +++++++++++++
4 files changed, 129 insertions(+), 15 deletions(-)
diff --git a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
index 89db303c..9e4590b5 100644
--- a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
+++ b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs
@@ -120,6 +120,30 @@ public class AsyncLogAppenderTests
Assert.That(observedResults, Has.All.True);
}
+ [Test]
+ public void Flush_WhenEntriesAlreadyProcessed_Should_Still_ReportSuccess()
+ {
+ using var appendCompleted = new ManualResetEventSlim();
+ var innerAppender = new SignalingAppender(appendCompleted);
+ using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 10);
+ var observedResults = new List();
+
+ asyncAppender.Append(new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Already processed", null, null));
+ Assert.That(appendCompleted.Wait(TimeSpan.FromSeconds(1)), Is.True);
+
+ asyncAppender.OnFlushCompleted += (_, eventArgs) => observedResults.Add(eventArgs.Success);
+
+ var result = asyncAppender.Flush(TimeSpan.FromSeconds(1));
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(result, Is.True);
+ Assert.That(observedResults, Has.Count.EqualTo(1));
+ Assert.That(observedResults, Has.All.True);
+ Assert.That(innerAppender.FlushCount, Is.EqualTo(1));
+ });
+ }
+
[Test]
public void Dispose_ShouldProcessRemainingEntries()
{
@@ -308,6 +332,32 @@ public class AsyncLogAppenderTests
}
}
+ private sealed class SignalingAppender : ILogAppender
+ {
+ private readonly ManualResetEventSlim _appendCompleted;
+
+ public SignalingAppender(ManualResetEventSlim appendCompleted)
+ {
+ _appendCompleted = appendCompleted;
+ }
+
+ public int FlushCount { get; private set; }
+
+ public void Append(LogEntry entry)
+ {
+ _appendCompleted.Set();
+ }
+
+ public void Flush()
+ {
+ FlushCount++;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+
private class ThrowingAppender : ILogAppender
{
public void Append(LogEntry entry)
diff --git a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
index c377acce..a3654f7a 100644
--- a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
+++ b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs
@@ -23,6 +23,7 @@ public sealed class AsyncLogAppender : ILogAppender
private readonly Action? _processingErrorHandler;
private readonly Task _processingTask;
private bool _disposed;
+ private int _isProcessingEntry;
private volatile bool _flushRequested;
///
@@ -140,6 +141,7 @@ public sealed class AsyncLogAppender : ILogAppender
// 请求刷新
_flushRequested = true;
+ TrySignalFlushCompletion();
try
{
@@ -166,6 +168,7 @@ public sealed class AsyncLogAppender : ILogAppender
{
try
{
+ Volatile.Write(ref _isProcessingEntry, 1);
_innerAppender.Append(entry);
}
catch (Exception ex)
@@ -173,18 +176,12 @@ public sealed class AsyncLogAppender : ILogAppender
// 后台消费失败只通过显式回调暴露,避免测试宿主将 stderr 误判为测试告警。
ReportProcessingError(ex);
}
-
- // 检查是否有刷新请求且通道已空
- if (_flushRequested && _channel.Reader.Count == 0)
+ finally
{
- _innerAppender.Flush();
-
- // 发出完成信号
- if (_flushSemaphore.CurrentCount == 0)
- {
- _flushSemaphore.Release();
- }
+ Volatile.Write(ref _isProcessingEntry, 0);
}
+
+ TrySignalFlushCompletion();
}
}
catch (OperationCanceledException)
@@ -209,6 +206,29 @@ public sealed class AsyncLogAppender : ILogAppender
}
}
+ ///
+ /// 在后台消费者已经处理完当前条目且队列为空时完成挂起的 Flush 请求。
+ ///
+ private void TrySignalFlushCompletion()
+ {
+ if (!_flushRequested)
+ {
+ return;
+ }
+
+ if (Volatile.Read(ref _isProcessingEntry) != 0 || _channel.Reader.Count != 0)
+ {
+ return;
+ }
+
+ _innerAppender.Flush();
+
+ if (_flushSemaphore.CurrentCount == 0)
+ {
+ _flushSemaphore.Release();
+ }
+ }
+
///
/// 上报后台处理异常,同时隔离观察者自身抛出的错误,避免终止处理循环。
/// 取消相关异常表示关闭流程中的预期控制流,不应被视为后台处理失败。
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 2c4d2b08..b782563c 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
@@ -7,12 +7,12 @@
## 当前恢复点
-- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-014`
-- 当前阶段:`Phase 14`
+- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-015`
+- 当前阶段:`Phase 15`
- 当前焦点:
- - 当前分支 PR #267 的 latest CodeRabbit unresolved threads、outside-diff comment 与 nitpick comment 已完成本地复核
- - 本轮优先收口仍然成立的 follow-up:`AsyncLogAppender` 接口路径重复触发 `OnFlushCompleted`、事件/XML 契约缺口、
- `PhaseChanged` 迁移文档说明与 `ai-plan` 基线表述歧义
+ - 当前分支 PR #267 的失败测试已通过 `$gframework-pr-review` 与本地整包测试完成复核
+ - 已确认并修复 `AsyncLogAppender.Flush()` 在“后台线程先清空队列”场景下可能超时返回 `false` 的竞态
+ - 已补上稳定回归测试,避免只在整包 `GFramework.Core.Tests` 里偶发暴露的刷新完成信号问题再次回归
- 下一轮默认恢复到 `MA0016` 或 `MA0002` 低风险批次;`MA0015` 与 `MA0077` 继续作为尾项顺手吸收
- `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
@@ -32,6 +32,8 @@
并同步更新 `GFramework.Godot` 订阅点、定向测试与 `docs/zh-CN` 示例
- 已完成当前 PR #267 review follow-up:修复 `AsyncLogAppender` 的 `ILogAppender.Flush()` 双重完成通知,并补齐
`PhaseChanged` / `CoroutineExceptionEventArgs` XML 文档、`PhaseChanged` 迁移说明和 `ai-plan` 基线注释
+- 已完成当前 PR #267 failed-test follow-up:修复 `AsyncLogAppender.Flush()` 在队列已被后台线程提前清空时仍可能
+ 等待满默认超时并返回 `false` 的竞态,并通过整包 `GFramework.Core.Tests` 重新验证
- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `9` 条;剩余 warning 集中在
`MA0016` 集合抽象接口、`MA0002` comparer 重载,以及 `MA0015` / `MA0077` 两个低数量尾项
@@ -62,6 +64,8 @@
非标准签名;`GFramework.Core` `net8.0` warnings-only 基线由 `15` 降至 `9`
- `RP-014` 使用 `gframework-pr-review` 复核当前分支 PR #267 的 latest head review threads、outside-diff comment 与
nitpick comment 后,确认 8 条高信号项中仍成立的是 1 个行为 bug 与 7 个文档/测试/跟踪缺口,并按最小改动收口
+- `RP-015` 使用 `$gframework-pr-review` 复核 PR #267 的 CTRF 失败测试评论后,确认 `AsyncLogAppender` 仍存在
+ “队列已空但 Flush 仍超时失败”的竞态;该问题在本地整包 `GFramework.Core.Tests` 中可复现,现已修复并补上稳定回归测试
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
## 当前风险
@@ -149,6 +153,11 @@
- 结果:`9 Warning(s)`,`0 Error(s)`;`AsyncLogAppender` 行为修复与 XML / 文档补充未引入新的 `GFramework.Core` `net8.0` 构建错误
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CoroutineSchedulerTests.Scheduler_Should_Raise_OnCoroutineException_With_EventArgs|FullyQualifiedName~AsyncLogAppenderTests.Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result|FullyQualifiedName~AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once|FullyQualifiedName~ArchitectureLifecycleBehaviorTests.InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs" -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`4 Passed`,`0 Failed`
+- `RP-015` 的验证结果:
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers --filter "FullyQualifiedName~AsyncLogAppenderTests"`
+ - 结果:`15 Passed`,`0 Failed`
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers`
+ - 结果:`1607 Passed`,`0 Failed`
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步
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 369c76ce..2bd3c807 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,40 @@
# Analyzer Warning Reduction 追踪
+## 2026-04-21 — RP-015
+
+### 阶段:PR #267 failed-test follow-up 收口(RP-015)
+
+- 触发背景:
+ - 用户指出“测试好像挂了”,按 `$gframework-pr-review` 重新抓取当前分支 PR #267 的 review / checks / CTRF 评论
+ - PR 评论里同时存在一次 `2143 passed / 0 failed` 与一次 `1 failed` 的 CTRF 报告;失败用例为
+ `AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once`
+- 复核过程:
+ - 先跑定向单测时该用例可以单独通过,因此继续核对 PR head commit 与本地整包测试,避免把旧评论误判成当前状态
+ - 在 `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers`
+ 下成功复现相同失败,确认问题仍存在于当前代码,而不是单纯的 PR 评论残留
+ - 同时发现当前沙箱内如果用 shell 循环反复启动 `dotnet test`,会触发 `MSBuild` named pipe `Permission denied`
+ 的环境噪音;后续验证改为单次命令并显式加 `--disable-build-servers`
+- 根因结论:
+ - `AsyncLogAppender.Flush()` 只依赖后台消费循环在处理完某个条目后检查 `_flushRequested`
+ - 当调用方执行 `Flush()` 前,后台线程已经把最后一个条目消费完并离开检查点时,`Flush()` 会一直等到默认超时,
+ 最终通过 `OnFlushCompleted` 发出一次 `Success=false` 的错误完成通知
+- 实施修复:
+ - 为 `AsyncLogAppender` 增加“当前是否仍有条目在途处理”的状态跟踪
+ - 抽出 `TrySignalFlushCompletion()`,让 `Flush()` 在请求发出后先做一次即时完成判定;后台循环在每次处理结束后也复用
+ 这条判定路径
+ - 在 `AsyncLogAppenderTests` 中新增 `Flush_WhenEntriesAlreadyProcessed_Should_Still_ReportSuccess`,稳定覆盖
+ “调用 Flush 前队列已被后台线程清空”的场景
+- 验证结果:
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers --filter "FullyQualifiedName~AsyncLogAppenderTests"`
+ - 结果:`15 Passed`,`0 Failed`
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --disable-build-servers`
+ - 结果:`1607 Passed`,`0 Failed`
+- 当前结论:
+ - PR #267 的 failed-test 信号不是纯粹的历史评论噪音,而是当前实现里仍存在的时序竞态
+ - 修复后该竞态已被稳定回归测试覆盖,当前 `GFramework.Core.Tests` 整包通过
+- 下一步建议:
+ - 若继续 analyzer warning reduction 主题,恢复到 `MA0016` / `MA0002` 低风险批次
+
## 2026-04-21 — RP-014
### 阶段:PR #267 review follow-up 收口(RP-014)