SqlConnection会被垃圾回收器(GC)释放吗?

14

免责声明:我知道在处理非托管资源时应该实现IDisposable. 其余代码应该是确定性的,并且使用using (...) { }(相当于try {} finally { Dispose(); })来确保尽快进行清理。此外,GC不会调用Dispose(),所以推荐的模式是重写Finalize()方法(在C#中使用析构语法),然后调用Dispose()。除非调用了GC.SuppressFinalize(),否则GC通常会调用Finalize()

问题:既然我已经解决了上述问题,现在我遇到了一个奇怪的情况,由于我无法控制的代码,我不能使用using(SqlConnection...){}.我通常可以执行确定性的Dispose(),但不能保证它。我使用反编译工具Reflector来查看SqlConnection,发现它使用了Dispose(),但除非我瞎了,否则没有终结器/析构函数(Finalize()~SqlConnection())。这意味着GC不会在我不能进行"清理"(将连接发送回池中)的情况下进行吗?我还没有找到任何确切的信息...

1个回答

11

嗯,它不会被丢弃,因为终结(finalisation)不是处理处置(disposal)。

System.ComponentModel.Component中有一个终结器(finaliser),但在SQLConnection的构造函数中被禁止。如果您从具有终结器的东西继承并且您100%确定您不需要它,则这是一个好主意,否则就不是一个好主意。在这种情况下,这是一个好主意。

请记住,SqlConnection是“真实”连接的包装器。实际上,它很可能是对表示不同连接状态的不同对象集的包装器。这是允许有效地池化“真实”连接的机制的一部分,每次调用Open()时,它都会从池中获取相关对象,并且每次调用Close()(无论是直接,通过Dispose()还是通过离开using的作用域)都会将其返回。

现在,请记住,只有直接持有未管理资源或其他GC不关心的内容的对象才需要进行终结。 SqlConnection持有一个对象,该对象可能(取决于SqlConnection的状态)是一个持有未受管资源的对象(或者更深层次的嵌套类)。因此,SqlConnection本身无需进行终结。考虑一个开放的SqlConnection可能停止成为开放的SqlConnection的三种可能方式:

  1. 调用Close()。这将立即将实际连接返回到池中(如果没有汇集,则关闭)。
  2. 调用Dispose()。这将调用具有相同效果的Close()
  3. 对象被垃圾回收。
现在,在第三种情况下,这个对象持有一个对真实连接对象的引用。它也是唯一持有该引用的对象。由此,该对象也将被垃圾回收。如果它有一个终结器(它可能有,虽然我不会假设没有更多聪明的技巧),那么这个终结器将导致它被放入终结器队列中,并最终完成终结操作。
如果 SqlConnection 有一个终结器,那么唯一的真实影响将是:
  1. 潜在的错误代码(在终止代码中处理可终结成员是很危险的,因为你不知道它们是否已经终结)。
  2. 潜在的减速(真实连接终结的结果是相同的,最好的情况只是减缓了终结和GC的过程)。
  3. 无事可做(真实连接将在没有任何帮助的情况下进行终结)。
因此,在 SqlConnection 上放置终结器是得不偿失的。而且,您的真实连接应该会最终完成终结。
话虽如此,这仍然远非理想状态,仍然非常可能泄漏连接。您能详细说明为什么不能调用 Close() 或自行处理吗?连接管理代码不能为您调用关闭操作吗(对象应该在某处结束其生命周期,并在那里关闭)?
是否需要将其保持活动状态以允许完成 IDataReader 或从 IDataReader 输入的对象?如果是这种情况,您可以使用 CommandBehavior.CloseConnection 标志,使关闭 (或处理) 读取器时关闭连接。后者是我唯一能想到必须让连接离开作用域而不被处理的情况。

"SqlConnection保存一个对象,该对象可能(取决于SqlConnection的状态)是保存非托管资源的对象" - 很好,我没有验证SqlConnection实际上保存了非托管资源。基本上,管理连接的代码是一个外部库,没有正确实现。我想我最终能够对其进行更改并完全避免此问题。 - Nelson Rothermel
很高兴听到这个消息。最终化在正确的对象上完成是非常好的,但如果最终化不够频繁(据我所见,它几乎从来没有发生过 - 或者更糟糕的是,在测试中发生了,而实际使用中没有发生),那么这并不能帮助你。 - Jon Hanna
+1个好答案。我对MSDN上的以下声明有些困惑,它表明SqlConnection是托管资源。“不要在类的Finalize方法中调用Connection、DataReader或任何其他托管对象的Close或Dispose。”来自这里 - Tim Schmelter
@TimSchmelter SqlConnection 类确实是托管对象,它通过一些托管层次的间接方式包装了执行实际的 1 和 0 传输的非托管对象。是的,在 finaliser 中永远不要处理(除非你知道该托管对象应该具有自己的 finaliser 但实际上没有)。通常这样做会干扰该对象的 finaliser,并且也应该是不必要的,因为该其他对象中已经有了 finaliser。 - Jon Hanna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接