fix(analyzer): 收敛 CoroutineScheduler 长方法 warning

- 重构 CoroutineScheduler 的启动与完成清理阶段,降低 MA0051 并保持取消与完成语义
- 补充辅助方法注释,保留标签分组、统计和等待者唤醒顺序
- 更新 analyzer warning reduction 的恢复点与验证记录
This commit is contained in:
GeWuYou 2026-04-21 11:17:47 +08:00
parent ec0c9a7bc8
commit f044aeb770
3 changed files with 306 additions and 122 deletions

View File

@ -211,58 +211,10 @@ public sealed class CoroutineScheduler(
return default;
}
if (_nextSlot >= _slots.Length)
{
Expand();
}
var handle = new CoroutineHandle(instanceId);
var slotIndex = _nextSlot++;
var slot = new CoroutineSlot
{
CancellationToken = cancellationToken,
Enumerator = coroutine,
State = CoroutineState.Running,
Handle = handle,
Priority = priority
};
if (cancellationToken.CanBeCanceled)
{
// 取消回调可能在任意线程触发,因此这里只做排队,真正清理由 Update 主线程完成。
slot.CancellationRegistration = cancellationToken.Register(() => _pendingKills.Enqueue(handle));
}
_slots[slotIndex] = slot;
_metadata[handle] = new CoroutineMetadata
{
ExecutionStage = executionStage,
Group = group,
Priority = priority,
SlotIndex = slotIndex,
StartTime = _timeSource.CurrentTime * 1000,
State = CoroutineState.Running,
Tag = tag
};
_completionSources[handle] =
new TaskCompletionSource<CoroutineCompletionStatus>(TaskCreationOptions.RunContinuationsAsynchronously);
_completionStatuses.Remove(handle);
if (!string.IsNullOrEmpty(tag))
{
AddTag(tag, handle);
}
if (!string.IsNullOrEmpty(group))
{
AddGroup(group, handle);
}
_statistics?.RecordStart(priority, tag);
ActiveCoroutineCount++;
var slotIndex = AllocateSlotIndex();
var slot = CreateRunningSlot(handle, coroutine, priority, cancellationToken);
RegisterStartedCoroutine(handle, slotIndex, slot, priority, tag, group);
Prewarm(slotIndex);
UpdateStatisticsSnapshot();
@ -662,70 +614,14 @@ public sealed class CoroutineScheduler(
CoroutineCompletionStatus completionStatus,
Exception? exception = null)
{
var slot = _slots[slotIndex];
if (slot == null)
if (!TryGetFinalizableCoroutine(slotIndex, out var slot, out var handle))
{
return;
}
var handle = slot.Handle;
if (!handle.IsValid)
{
return;
}
if (_metadata.TryGetValue(handle, out var meta))
{
if (meta.State == CoroutineState.Paused && _pausedCount > 0)
{
_pausedCount--;
}
var executionTime = _timeSource.CurrentTime * 1000 - meta.StartTime;
switch (completionStatus)
{
case CoroutineCompletionStatus.Completed:
meta.State = CoroutineState.Completed;
_statistics?.RecordComplete(executionTime, meta.Priority, meta.Tag);
break;
case CoroutineCompletionStatus.Faulted:
meta.State = CoroutineState.Completed;
_statistics?.RecordFailure(meta.Priority, meta.Tag);
break;
case CoroutineCompletionStatus.Cancelled:
meta.State = CoroutineState.Cancelled;
break;
default:
throw new ArgumentOutOfRangeException(
nameof(completionStatus),
completionStatus,
"Unsupported coroutine completion status.");
}
}
DisposeSlotResources(slot);
_slots[slotIndex] = null;
if (ActiveCoroutineCount > 0)
{
ActiveCoroutineCount--;
}
RemoveTag(handle);
RemoveGroup(handle);
_metadata.Remove(handle);
WakeWaiters(handle);
if (_completionSources.Remove(handle, out var source))
{
source.TrySetResult(completionStatus);
}
RecordCompletionStatus(handle, completionStatus);
UpdateCompletionMetadata(handle, completionStatus);
ReleaseCompletedCoroutine(slotIndex, slot, handle);
CompleteCoroutineLifecycle(handle, completionStatus);
OnCoroutineFinished?.Invoke(handle, completionStatus, exception);
}
@ -799,6 +695,139 @@ public sealed class CoroutineScheduler(
}
}
/// <summary>
/// 为新协程分配槽位索引,并在需要时扩容槽位数组。
/// </summary>
/// <returns>可写入的新槽位索引。</returns>
private int AllocateSlotIndex()
{
if (_nextSlot >= _slots.Length)
{
Expand();
}
return _nextSlot++;
}
/// <summary>
/// 创建处于运行态的协程槽位,并在需要时挂接跨线程取消回调。
/// </summary>
/// <param name="handle">新协程句柄。</param>
/// <param name="coroutine">协程枚举器。</param>
/// <param name="priority">协程优先级。</param>
/// <param name="cancellationToken">外部取消令牌。</param>
/// <returns>已初始化的协程槽位。</returns>
private CoroutineSlot CreateRunningSlot(
CoroutineHandle handle,
IEnumerator<IYieldInstruction> coroutine,
CoroutinePriority priority,
CancellationToken cancellationToken)
{
var slot = new CoroutineSlot
{
CancellationToken = cancellationToken,
Enumerator = coroutine,
State = CoroutineState.Running,
Handle = handle,
Priority = priority
};
RegisterCancellationCallback(slot, handle, cancellationToken);
return slot;
}
/// <summary>
/// 为支持取消的协程注册待终止排队回调。
/// </summary>
/// <param name="slot">目标协程槽位。</param>
/// <param name="handle">协程句柄。</param>
/// <param name="cancellationToken">外部取消令牌。</param>
private void RegisterCancellationCallback(
CoroutineSlot slot,
CoroutineHandle handle,
CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled)
{
return;
}
// 取消回调可能在任意线程触发,因此这里只做排队,真正清理由 Update 主线程完成。
slot.CancellationRegistration = cancellationToken.Register(() => _pendingKills.Enqueue(handle));
}
/// <summary>
/// 将新协程写入调度器的槽位、元数据、标签分组和完成状态跟踪结构。
/// </summary>
/// <param name="handle">协程句柄。</param>
/// <param name="slotIndex">槽位索引。</param>
/// <param name="slot">已初始化的协程槽位。</param>
/// <param name="priority">协程优先级。</param>
/// <param name="tag">可选标签。</param>
/// <param name="group">可选分组。</param>
private void RegisterStartedCoroutine(
CoroutineHandle handle,
int slotIndex,
CoroutineSlot slot,
CoroutinePriority priority,
string? tag,
string? group)
{
_slots[slotIndex] = slot;
_metadata[handle] = CreateCoroutineMetadata(slotIndex, priority, tag, group);
ResetCompletionTracking(handle);
if (!string.IsNullOrEmpty(tag))
{
AddTag(tag, handle);
}
if (!string.IsNullOrEmpty(group))
{
AddGroup(group, handle);
}
_statistics?.RecordStart(priority, tag);
ActiveCoroutineCount++;
}
/// <summary>
/// 创建新协程的初始元数据。
/// </summary>
/// <param name="slotIndex">槽位索引。</param>
/// <param name="priority">协程优先级。</param>
/// <param name="tag">可选标签。</param>
/// <param name="group">可选分组。</param>
/// <returns>与新槽位对应的元数据对象。</returns>
private CoroutineMetadata CreateCoroutineMetadata(
int slotIndex,
CoroutinePriority priority,
string? tag,
string? group)
{
return new CoroutineMetadata
{
ExecutionStage = executionStage,
Group = group,
Priority = priority,
SlotIndex = slotIndex,
StartTime = _timeSource.CurrentTime * 1000,
State = CoroutineState.Running,
Tag = tag
};
}
/// <summary>
/// 重置协程完成跟踪,使复用句柄不会携带上一轮完成结果。
/// </summary>
/// <param name="handle">协程句柄。</param>
private void ResetCompletionTracking(CoroutineHandle handle)
{
_completionSources[handle] =
new TaskCompletionSource<CoroutineCompletionStatus>(TaskCreationOptions.RunContinuationsAsynchronously);
_completionStatuses.Remove(handle);
}
/// <summary>
/// 释放单个槽位持有的资源。
/// </summary>
@ -824,6 +853,125 @@ public sealed class CoroutineScheduler(
slot.Waiting = null;
}
/// <summary>
/// 读取可被完成处理的协程槽位与句柄。
/// 当槽位已空或句柄已失效时,说明该协程已经被其他路径清理,无需重复执行结束逻辑。
/// </summary>
/// <param name="slotIndex">槽位索引。</param>
/// <param name="slot">若成功则返回槽位。</param>
/// <param name="handle">若成功则返回句柄。</param>
/// <returns>当存在可完成的协程时返回 <see langword="true" />。</returns>
private bool TryGetFinalizableCoroutine(int slotIndex, out CoroutineSlot slot, out CoroutineHandle handle)
{
var candidate = _slots[slotIndex];
if (candidate == null)
{
slot = null!;
handle = default;
return false;
}
handle = candidate.Handle;
if (!handle.IsValid)
{
slot = null!;
return false;
}
slot = candidate;
return true;
}
/// <summary>
/// 根据最终状态更新协程元数据与统计信息。
/// </summary>
/// <param name="handle">协程句柄。</param>
/// <param name="completionStatus">最终结果。</param>
private void UpdateCompletionMetadata(CoroutineHandle handle, CoroutineCompletionStatus completionStatus)
{
if (!_metadata.TryGetValue(handle, out var meta))
{
return;
}
if (meta.State == CoroutineState.Paused && _pausedCount > 0)
{
_pausedCount--;
}
ApplyCompletionMetadata(meta, completionStatus);
}
/// <summary>
/// 将最终结果映射到元数据状态和统计记录。
/// </summary>
/// <param name="meta">协程元数据。</param>
/// <param name="completionStatus">最终结果。</param>
private void ApplyCompletionMetadata(CoroutineMetadata meta, CoroutineCompletionStatus completionStatus)
{
var executionTime = _timeSource.CurrentTime * 1000 - meta.StartTime;
switch (completionStatus)
{
case CoroutineCompletionStatus.Completed:
meta.State = CoroutineState.Completed;
_statistics?.RecordComplete(executionTime, meta.Priority, meta.Tag);
break;
case CoroutineCompletionStatus.Faulted:
meta.State = CoroutineState.Completed;
_statistics?.RecordFailure(meta.Priority, meta.Tag);
break;
case CoroutineCompletionStatus.Cancelled:
meta.State = CoroutineState.Cancelled;
break;
default:
throw new ArgumentOutOfRangeException(
nameof(completionStatus),
completionStatus,
"Unsupported coroutine completion status.");
}
}
/// <summary>
/// 释放已结束协程占用的槽位和索引结构。
/// </summary>
/// <param name="slotIndex">槽位索引。</param>
/// <param name="slot">已结束的协程槽位。</param>
/// <param name="handle">协程句柄。</param>
private void ReleaseCompletedCoroutine(int slotIndex, CoroutineSlot slot, CoroutineHandle handle)
{
DisposeSlotResources(slot);
_slots[slotIndex] = null;
if (ActiveCoroutineCount > 0)
{
ActiveCoroutineCount--;
}
RemoveTag(handle);
RemoveGroup(handle);
_metadata.Remove(handle);
}
/// <summary>
/// 完成协程的等待者唤醒、任务结果和完成历史记录。
/// </summary>
/// <param name="handle">协程句柄。</param>
/// <param name="completionStatus">最终结果。</param>
private void CompleteCoroutineLifecycle(CoroutineHandle handle, CoroutineCompletionStatus completionStatus)
{
WakeWaiters(handle);
if (_completionSources.Remove(handle, out var source))
{
source.TrySetResult(completionStatus);
}
RecordCompletionStatus(handle, completionStatus);
}
/// <summary>
/// 唤醒所有等待目标协程完成的协程。
/// </summary>

View File

@ -7,21 +7,20 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-006`
- 当前阶段:`Phase 6`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-007`
- 当前阶段:`Phase 7`
- 当前焦点:
- 已完成 `GFramework.Core/StateManagement/Store.cs``MA0051` 收口:将 `Dispatch` 拆分为“进入分发域 / 提交结果 / 退出分发域”
三个辅助阶段,并将 reducer 快照创建拆分为精确匹配与多态匹配两条路径
- 本轮保持 `Store` 的锁顺序、middleware 执行时机、batch 通知折叠和多态 reducer 排序规则不变,未改公共 API
- 下一轮若继续推进,优先只处理 `GFramework.Core/Coroutine/CoroutineScheduler.cs` 的剩余 `MA0051`,不回到已完成的
`Store``PauseStackManager`
- 已完成 `GFramework.Core/Coroutine/CoroutineScheduler.cs``MA0051` 收口:将 `Run` 拆分为槽位分配、运行槽创建和启动注册阶段,
`FinalizeCoroutine` 拆分为可完成目标读取、完成元数据更新、资源释放和生命周期收尾阶段
- 本轮保持取消回调入队、统计记录、标签/分组清理、等待者唤醒和完成任务语义不变,未改公共 API
- 当前 `MA0051` 主线已完成;下一轮若继续推进,应先判断剩余 `MA0048``MA0046``MA0002``MA0016` 是否值得继续低风险收敛
## 当前状态摘要
- 已完成 `GFramework.Core``GFramework.Cqrs``GFramework.Godot` 与部分 source generator 的低风险 warning 清理
- 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险
- 当前 `PauseStackManager``Store` 的长方法 warning 已从 active 入口移除;主题内剩余 warning 主要集中在
`GFramework.Core/Coroutine/CoroutineScheduler.cs`、文件/类型命名冲突、delegate 形状和少量公共集合抽象接口问题
- 当前 `PauseStackManager``Store``CoroutineScheduler` 的长方法 warning 已从 active 入口移除;主题内剩余 warning
主要集中在文件/类型命名冲突、delegate 形状、字符串 comparer 重载和少量公共集合抽象接口问题
## 当前活跃事实
@ -33,6 +32,8 @@
- `RP-005` 已在不改公共 API 的前提下完成 `PauseStackManager` 两个 `MA0051` 的结构拆分,并补充销毁通知回归测试
- `RP-006` 已在不改公共 API 的前提下完成 `Store` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证 dispatch、
多态 reducer 匹配与历史语义未回归
- `RP-007` 已在不改公共 API 的前提下完成 `CoroutineScheduler` 两个 `MA0051` 的结构拆分,并通过定向 build/test 验证
调度、取消与完成状态语义未回归
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
## 当前风险
@ -71,11 +72,16 @@
- 结果:`25 Warning(s)``0 Error(s)``Store.cs` 已不再出现在 `MA0051` 列表中
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~StoreTests -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo`
- 结果:`30 Passed``0 Failed`
- `RP-007` 的定向验证结果:
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo -clp:"Summary;WarningsOnly"`
- 结果:`23 Warning(s)``0 Error(s)``CoroutineScheduler.cs` 已不再出现在 `MA0051` 列表中
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~CoroutineScheduler -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo`
- 结果:`34 Passed``0 Failed`
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步
1. 若要继续该主题,先读 active tracking再按需展开历史归档中的 warning 热点与验证记录
2. 优先在 `GFramework.Core/Coroutine/CoroutineScheduler.cs``Run``FinalizeCoroutine` 两个 `MA0051`
中继续,保持“单文件、单 warning family”的节奏
2. 先基于当前 `23 Warning(s)` 的唯一源位置清单,判断 `MA0048` 文件命名冲突与 `MA0046` delegate 形状是否存在低风险切入点,
再决定是否开启下一轮 warning family 收敛
3. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/`

View File

@ -1,5 +1,35 @@
# Analyzer Warning Reduction 追踪
## 2026-04-21 — RP-007
### 阶段CoroutineScheduler `MA0051` 收口RP-007
- 依据 active tracking 中“继续只选一个 `GFramework.Core` 结构性切入点”的约束,本轮选择
`GFramework.Core/Coroutine/CoroutineScheduler.cs`,因为剩余两个 `MA0051` 都集中在协程启动与完成清理路径,且已有
`CoroutineSchedulerTests``CoroutineSchedulerAdvancedTests` 覆盖句柄创建、取消、完成状态、标签分组和等待语义
- 将 `Run` 拆分为:
- `AllocateSlotIndex`
- `CreateRunningSlot`
- `RegisterCancellationCallback`
- `RegisterStartedCoroutine`
- `CreateCoroutineMetadata`
- `ResetCompletionTracking`
- 将 `FinalizeCoroutine` 拆分为:
- `TryGetFinalizableCoroutine`
- `UpdateCompletionMetadata`
- `ApplyCompletionMetadata`
- `ReleaseCompletedCoroutine`
- `CompleteCoroutineLifecycle`
- 保持取消回调只做跨线程入队、`Prewarm` 时机、统计记录文本、`RemoveTag` / `RemoveGroup` / `WakeWaiters` 顺序以及
`OnCoroutineFinished` 的同步触发时机不变,只收缩主方法长度并补齐辅助方法意图注释
- 验证通过:
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo -clp:"Summary;WarningsOnly"`
- 结果:`23 Warning(s)``0 Error(s)``CoroutineScheduler.cs` 已不再出现在 `MA0051` 列表
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter FullyQualifiedName~CoroutineScheduler -p:RestoreFallbackFolders="" -p:RestorePackagesPath=<linux-nuget-cache> -nologo`
- 结果:`34 Passed``0 Failed`
- 当前 `MA0051` 主线已经在本主题下完成;下一步若继续,应先重新评估剩余 `MA0048``MA0046``MA0002``MA0016`
收敛价值与改动风险,再决定是否开启下一轮 warning family
## 2026-04-21 — RP-006
### 阶段Store `MA0051` 收口RP-006