← 中文资源

简体中文版本

C# ObjectDisposedException — 使用了已释放资源

通过尊重 using 作用域、不捕获已释放的 HttpClient 或流,并明确所有权,修复 ObjectDisposedException。

阅读英文原文

症状

本篇调试指南针对“C# ObjectDisposedException — 使用了已释放资源”。内容涵盖可观察的症状、可复现的诊断路径、根本原因、具体修复方式,以及如何验证修复并防止回归。

可观察到的症状包括:

  • System.ObjectDisposedException: Cannot access a disposed object
  • HttpResponseMessage 释放后仍使用其响应流
  • 注入的 HttpClient 首次可用后连续失败

诊断

先在隔离环境中复现故障,再按顺序执行以下检查,逐步缩小范围。

步骤 1

dotnet run 2>&1 | grep -A 3 ObjectDisposedException

消息中的 ObjectName 属性会指出被释放的实例,从而定位其所有者。

步骤 2

grep -rn "using var\|using (" *.cs | head

方法作用域的 using var 会在方法结束时释放;比其更长寿的捕获引用即失败。

常见诱因(按出现频率排序):

  • 资源在 using 块或 Dispose 调用结束后仍被使用
  • 流读取器比拥有该流的响应更长寿
  • HttpClient 或 DbContext 被释放时调用方仍持有引用

根因

IDisposable 对象在释放后即不可用。当代码调用已释放资源的成员时抛 ObjectDisposedException,通常源于所有权或生命周期判断错误。

修复

让资源在使用期内保持存活:在离开 using 作用域前复制所需数据,HttpClient 用 IHttpClientFactory,DbContext 作用域限定为请求级。

注意:为规避异常而移除 using 会导致资源泄漏;应修正生命周期,而非放弃释放。

// ✅ HttpClient via factory, no manual dispose
public sealed class ApiClient(IHttpClientFactory f)
{
    public async Task<string> GetAsync(CancellationToken ct)
    {
        var client = f.CreateClient("api");
        return await client.GetStringAsync("/health", ct);
    }
}

// ✅ read the body before the response leaves the using scope
using var resp = await http.GetAsync(url);
var text = await resp.Content.ReadAsStringAsync(); // body copied out
// return text and keep using it after resp is disposed

验证

重跑该场景并确认无 ObjectDisposedException;用二次调用的诊断测试证明访问时资源仍有效。

修复后补充一条回归测试,复现原始症状,防止问题再次出现。

这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。