使用async/await与CancellationTokenSource时的内存泄漏修复求助
内存泄漏修复方案及CancellationToken使用规范
针对你遇到的.NET Framework 4.6应用内存泄漏(SQLParameter、_SQLMetaData对象累积)问题,结合你提供的内存分析数据,核心修复方向是全面规范CancellationToken的传递,并检查DbContext生命周期,具体如下:
一、必须给所有支持CancellationToken的异步方法传递令牌
从你提供的VS诊断结果能明显看到:给FirstOrDefaultAsync传入CancellationToken后,内存增长速度显著放缓。这说明未传递CT会导致异步任务无法及时响应取消,底层数据库资源(SQLParameter等)被长期持有,无法被GC回收。
需要覆盖的场景包括:
- EF Core/EF6的异步数据库操作:
FirstOrDefaultAsync、SaveChangesAsync、ToListAsync等,必须显式传递CT - 自定义异步方法:
validation1、validation2、apicall这类内部包含异步操作(数据库查询、HTTP请求)的方法,必须添加CancellationToken参数,并传递给底层依赖的异步API - 延迟操作:
Task.Delay必须传入CT,确保取消信号能及时终止延迟,避免循环阻塞
二、具体代码修复示例
1. 修复Jobsstore的getjobID方法
补传CT给EF查询:
public class Jobsstore { async Task<Guid?> getjobID(CancellationToken ct) { // 给FirstOrDefaultAsync传入CancellationToken var jobHeader = await dbContext.Tbl_Jobs.Where(<criteria>).FirstOrDefaultAsync(ct).ConfigureAwait(false); if(jobHeader != null) { jobHeader.LastGeneratorPickupDateTime = DateTime.Now; jobHeader.UpdateDateTime = DateTime.Now; await dbContext.SaveChangesAsync(ct).ConfigureAwait(false); return jobHeader.GUID; } else { return null; } } }
2. 修复ServerClass的Start方法
给所有异步方法传递CT,并修改Task.Delay:
public class ServerClass { public async Task Start(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { _jobID = await Jobsstore.getjobID(cancellationToken).ConfigureAwait(false); } catch (Exception ex) { //Log Error } if (!_jobID.HasValue) { // 给Task.Delay传入CancellationToken,响应取消信号 await Task.Delay(xxx, cancellationToken).ConfigureAwait(false); continue; } try { // 给自定义异步方法传递CancellationToken await validation1(cancellationToken).ConfigureAwait(false); await validation2(cancellationToken).ConfigureAwait(false); await apicall(cancellationToken).ConfigureAwait(false); } catch { //Log Error await Task.Delay(xxx, cancellationToken).ConfigureAwait(false); } } } }
3. 修复自定义异步方法示例(以validation1为例)
public async Task validation1(CancellationToken ct) { // 内部异步操作必须传递CT var data = await dbContext.Tbl_Validation.Where(<criteria>).FirstOrDefaultAsync(ct).ConfigureAwait(false); // 其他逻辑... } public async Task apicall(CancellationToken ct) { using(var client = new HttpClient()) { var content = new StringContent("request body"); // 给HTTP请求传递CT var response = await client.PostAsync("https://api.example.com", content, ct).ConfigureAwait(false); // 处理响应... } }
三、DbContext生命周期检查(关键修复点)
如果你的DbContext是单例或长生命周期持有,这会是内存泄漏的核心原因:EF会缓存查询计划、实体对象、SQLParameter等,长期持有DbContext会导致这些对象无法被GC回收。
修复方式:
- 对于后台任务,每次操作创建新的DbContext(使用
using包裹):
async Task<Guid?> getjobID(CancellationToken ct) { using(var dbContext = new YourDbContext()) { var jobHeader = await dbContext.Tbl_Jobs.Where(<criteria>).FirstOrDefaultAsync(ct).ConfigureAwait(false); // ...其他逻辑 await dbContext.SaveChangesAsync(ct).ConfigureAwait(false); } }
- 如果使用依赖注入,配置DbContext为
Transient或Scoped(注意后台任务的Scope管理)
四、额外验证步骤
- 使用内存分析器重新运行测试,确认SQLParameter等对象是否不再累积
- 检查是否有未处理的
OperationCanceledException,确保取消逻辑正常触发 - 验证所有异步操作在收到取消信号后能及时终止,避免资源长期占用
内容的提问来源于stack exchange,提问作者Yoshimori
相关产品推荐
相关产品推荐

