Files
FlowScope/docs/fixes/audio-cancellation-semantics-fix.md
2026-05-20 11:57:00 +08:00

103 lines
3.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AudioService 取消语义修复说明
日期2026-05-20
范围:`My project/Assets/FlowScope/Runtime/Audio/AudioService.cs`
优先级P0 小修复
结论:建议立即顺手修复
## 问题概述
`AudioService.PlayBgmAsync``AudioService.PlaySfxAsync` 支持传入 `CancellationToken`,但当前内部 `LoadClipAsync` 会捕获所有异常,并统一包装为 `InvalidOperationException`
这会导致调用方主动取消音频加载时,拿到的不是 `OperationCanceledException`,而是普通失败异常。
## 影响
取消语义被破坏后,调用方无法可靠区分以下两类情况:
- 生命周期或用户操作导致的正常取消。
- 资源缺失、Addressables 失败、类型不匹配等真实加载错误。
可能造成的问题:
- 正常取消被记录成错误日志。
- UI 或业务层误判为资源加载失败。
- 后续重试、错误兜底、统计埋点出现噪音。
- 框架内不同服务的取消语义不一致,增加异步生命周期排查成本。
## 推荐修改
`AudioService.LoadClipAsync` 中单独保留 `OperationCanceledException`,不要包装。
目标代码形态:
```csharp
private async Task<IResourceHandle<AudioClip>> LoadClipAsync(
string key,
CancellationToken cancellationToken)
{
try
{
var handle = await _resources.LoadAsync<AudioClip>(key, cancellationToken);
return ValidateLoadedClip(key, handle);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception exception)
{
throw new InvalidOperationException($"Failed to load audio clip '{key}'.", exception);
}
}
```
## 测试建议
`AudioServiceTests` 中补充异步取消测试。
建议覆盖:
1. `PlayBgmAsync_WhenCanceled_ThrowsOperationCanceledException`
2. `PlaySfxAsync_WhenCanceled_ThrowsOperationCanceledException`
测试要点:
- 创建 `CancellationTokenSource`
- 调用 `Cancel()`
- 调用 `PlayBgmAsync``PlaySfxAsync`
- 断言抛出 `OperationCanceledException`
- 断言没有创建新的 `AudioSource`
示例结构:
```csharp
[UnityTest]
public IEnumerator PlayBgmAsync_WhenCanceled_ThrowsOperationCanceledException()
{
var service = CreateService();
using var cts = new CancellationTokenSource();
cts.Cancel();
var sourceCountBeforePlay = Object.FindObjectsOfType<AudioSource>().Length;
yield return ThrowsAsync<OperationCanceledException>(
service.PlayBgmAsync("bgm-a", cancellationToken: cts.Token));
Assert.That(Object.FindObjectsOfType<AudioSource>().Length, Is.EqualTo(sourceCountBeforePlay));
}
```
如果现有测试工具方法只支持 `Func<Task>`,也可以按当前测试文件风格新增一个接收 `Task``ThrowsAsync<TException>` helper。
## 验收标准
- `PlayBgmAsync` 在取消时抛出 `OperationCanceledException`
- `PlaySfxAsync` 在取消时抛出 `OperationCanceledException`
- 取消时不创建 `AudioSource`
- 取消时不把异常包装成 `InvalidOperationException`
- 现有 PlayMode 音频测试继续通过。
## 后续备注
该问题不是架构阻塞项,但修复成本很低。建议本轮直接修掉,避免后续异步服务取消语义不一致。