为什么 FileStream
不关会导致内存占用持续上涨
根本原因不是文件句柄本身吃内存,而是未释放的
FileStream持有底层
SafeFileHandle,进而阻止 GC 回收关联的缓冲区(尤其是启用了
bufferSize且值较大时)和异步 I/O 状态对象。常见于用
using包裹失败、或在异常路径中漏掉
Dispose()。
实操建议:
所有手动创建的FileStream必须用
using块包裹,哪怕只是读几行——别信“小文件无所谓” 避免在构造函数里传
FileShare.None后长期持有实例;若需复用,改用
File.OpenRead()/
File.OpenWrite()并确保上层控制生命周期 检查是否误用了
File.ReadAllBytes()或
File.ReadAllText()处理大文件:它们会把整个内容加载进托管堆,且无流式节制
MemoryCache
缓存文件内容却没设过期策略
很多人用
MemoryCache存
byte[]或
string表示文件内容,但忘了默认不淘汰——缓存键不变,数据就永远留着,GC 不动它,内存只增不减。
实操建议:
显式设置absoluteExpiration或
slidingExpiration,哪怕只是
TimeSpan.FromMinutes(5)对大文件内容,优先考虑缓存
FileInfo或哈希值,而非原始字节;真要缓存内容,加
size配置项限制总容量(
MemoryCacheOptions.SizeLimit) 用
GetOrCreateAsync时注意:如果工厂方法抛异常,缓存不会写入,但调用方可能重试多次,导致重复加载——应在工厂内部做 try/catch + fallback
异步文件操作中 async/await
链断裂引发资源滞留
典型现象是 CPU 不高、线程数正常,但私有字节数(Private Bytes)缓慢爬升,尤其在 ASP.NET Core 中高频上传/下载场景。根源常是
await后续丢失上下文,导致
FileStream的终结器线程无法及时触发
Dispose,缓冲区卡在 finalizer queue。
实操建议:
绝不写stream.ReadAsync(...).GetAwaiter().GetResult()这类同步等待——它会阻塞线程并可能破坏异步流的资源清理链 在
async void方法(如事件处理器)中做文件操作?立刻改掉。必须用
async Task,让调用方能 await 和捕获异常 使用
FileStream构造时显式传
useAsync: true(.NET 5+ 默认开启),并确认你没有在
ConfigureAwait(false)后又意外切回 UI/ASP.NET 上下文导致死锁式等待
用 Process.Start("notepad.exe", path) 打开文件却不跟踪子进程
这不是 .NET 文件 API 的问题,但极易被归为“文件操作泄漏”:启动外部进程打开文件后,若不保存
Process实例、也不处理其
Exited事件或调用
WaitForExit(),该进程的句柄会持续占用非托管内存,且 Windows 不会自动回收其映射的文件视图(
MapViewOfFile)。
实操建议:
启动进程后立即保存Process引用,必要时在父逻辑结束前调用
process.Kill()(注意权限) 避免用
Process.Start()直接打开用户文档——改用
ShellExecute(P/Invoke
ShellExecuteEx并设
SEE_MASK_NOCLOSEPROCESS)可获得更可控的句柄 若只是想预览,优先走
Windows.System.Launcher.LaunchFileAsync()(UWP/WinUI),它由系统托管生命周期
最麻烦的从来不是哪行代码漏了
Dispose,而是多个小疏忽叠在一起:缓存没限大小 + 流没用
using+ 异步链断了 + 外部进程失控——它们各自只涨一点内存,合起来就让诊断工具看不出主因。
