测试时出现奇怪的资源释放行为,添加Thread.Sleep(1)即可修复,是什么原因?
问题根因分析
核心问题出在异步调用的时序错误和流生命周期管理错误两个方面:
- 异步方法未等待执行:你声明
ExtractFilesFromZipAndWriteToGivenZipArchive是异步方法,但代码中调用时没有加await关键字,调用后直接进入后续逻辑。当后台异步写入Zip归档的操作还未执行完成时,主线程已经走到了return逻辑,甚至已经触发了ZipArchive和MemoryStream的自动释放,导致Zip文件的结构写入不完整。
单独测试时系统资源充足,未被等待的异步操作刚好能在流释放前执行完成,所以不会报错;批量测试时CPU调度压力大,异步操作的执行优先级被压低,赶不上流释放的时序,就会出现「输入字符串格式不正确」「无法读取Zip文件」这类随机错误。 - 返回的MemoryStream生命周期错误:你在using块中创建了
ms,但直接在using块内部将其返回,当方法执行完毕离开using作用域时,ms会被自动释放,后续调用方拿到的是已被释放的MemoryStream,读取时自然会出现各类异常。
为什么1毫秒休眠可以临时解决问题
你添加Thread.Sleep(1)相当于人为阻塞了当前线程的执行,给后台未被等待的异步写入操作留出了足够的执行时间窗,刚好在ms被释放前完成了所有Zip结构的写入操作,所以看起来问题被解决了。但这完全是依赖执行时序的巧合,属于治标不治本的临时方案,在不同负载、不同环境下仍然会大概率复现问题。
正确修复方案
- 给异步方法调用加
await,确保所有Zip写入操作完成后再进入后续逻辑:
// 原有错误调用 // ExtractFilesFromZipAndWriteToGivenZipArchive(file, archive, i, cancellationToken); // 修正后 await ExtractFilesFromZipAndWriteToGivenZipArchive(file, archive, i, cancellationToken);
- 调整流返回逻辑,不要直接返回using块内的MemoryStream,避免返回的流被自动释放:
public async Task<MemoryStream> Handle(MultipleTypesExportQuery request, CancellationToken cancellationToken) { var stepwiseData = await HandleStepwise(request.RainmeterId, request.StepwiseQueries, cancellationToken); var periodicData = await HandlePeriodic(request.RainmeterId, request.PeriodicQueries, cancellationToken); var data = new List<MemoryStream>(); data.AddRange(stepwiseData); data.AddRange(periodicData); await using (var ms = new MemoryStream()) using (var archive = new ZipArchive(ms, ZipArchiveMode.Create,false)) { int i = 0; foreach (var d in data) { d.Open(); d.Position = 0; var file = ZipFile.Read(d); // 加await等待异步方法执行完成 await ExtractFilesFromZipAndWriteToGivenZipArchive(file, archive, i, cancellationToken); i++; file.Dispose(); d.Dispose(); } // 主动释放archive,确保所有Zip尾信息都写入ms archive.Dispose(); // 复制流内容到新的MemoryStream,设置起始位置为0后返回 var resultStream = new MemoryStream(ms.ToArray()); resultStream.Position = 0; return resultStream; } }
内容的提问来源于stack exchange,提问作者RageZ
相关产品推荐
相关产品推荐

