永不完成的TaskCompletionSource是否会引发内存泄漏问题?
结论
你提供的写法确实存在内存泄漏风险,风险的核心原因来自原始长耗时Task的引用链持有。
泄漏原因说明
- 调用
MuteOnDestroy扩展方法时,内部创建的TaskCompletionSource<T>(以下简称TCS)会被ContinueWith的回调闭包捕获,而这个回调会被原始的LongAction返回的Task持有,直到LongAction执行完成。 - 被await的TCS.Task会持有
RunExample异步方法的状态机实例,状态机作为实例方法的状态容器,又会持有你的组件实例本身。 - 只要
LongAction还未执行完成,哪怕destroyToken已经被标记为取消、你的组件已经没有其他外部引用,整条引用链(LongAction Task→ContinueWith回调→TCS→TCS.Task→异步状态机→组件实例)都会被GC标记为存活,不会被回收,产生内存泄漏。如果LongAction永久不完成,这段内存会被永久占用。 - 就算
LongAction最终执行完成,回调发现token已取消、未给TCS设置任何状态,此时TCS没有其他引用的情况下才会和状态机一起被回收,但在LongAction运行的这段时间内,泄漏依然存在。
优化方案
你当前的实现思路是用「永远不完成的Task」阻止后续逻辑执行,这个思路本身就容易引发泄漏问题。更合理的实现是让所有Task正常结束,主动判断组件状态决定是否执行后续逻辑,既满足需求也不会产生泄漏,示例如下:
// 扩展方法:吞掉取消异常,返回默认值 public static class TaskExtension { public static async Task<T> SuppressCancellation<T>(this Task<T> task) { try { return await task; } catch (OperationCanceledException) { return default; } } } // 业务代码调整 private CancellationTokenSource destroyTokenSource = new CancellationTokenSource(); private async void RunExample() { // 把销毁token传给长耗时任务,支持提前终止任务执行 var result = await LongAction(destroyTokenSource.Token).SuppressCancellation(); // 主动判断组件是否已销毁,决定是否执行后续逻辑 if (destroyTokenSource.IsCancellationRequested) return; // ... 只有组件未销毁才会执行的后续逻辑 } // 长耗时任务支持传入取消token,内部可以在关键点判断token状态提前终止 private async Task<bool> LongAction(CancellationToken token = default) { // 长耗时逻辑中插入token判断,比如: // await Task.Delay(5000, token); // if (token.IsCancellationRequested) return false; return true; }
另外注意两点:
- 你原有的实现只是忽略了长耗时任务的结果,并没有真的停止
LongAction本身的执行,把CancellationToken传入LongAction内部才能实现任务的提前终止,减少不必要的性能损耗。 - 尽量避免使用
async void方法,除非是UI事件处理器这类场景,async void方法的未处理异常会直接抛出到同步上下文,容易导致进程崩溃,建议改为async Task。
内容的提问来源于stack exchange,提问作者Dem0n13
相关产品推荐
相关产品推荐

