当我无法显式调用Dispose()方法时,在语句中创建的IDisposable对象会发生什么?

3

假设我正在使用Sharepoint(这也适用于其他对象模型),并且在语句中间调用了一个方法,例如“OpenWeb()”,该方法创建了一个IDisposable SPWeb对象。现在,由于我没有对SPWeb对象的引用,因此无法调用Dispose()方法。 那么我需要担心内存泄漏吗?

SPUser spUser = SPControl.GetContextSite(HttpContext.Current).OpenWeb().SiteUsers[@"foo\bar"];

我知道我可以将语句分成多行,并获取SPWeb引用以调用Dispose:

SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb();
SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
spWeb.Dispose();

请记住,我的问题不是关于美学方面的,而是关于无法显式调用Dispose()的IDisposable对象会发生什么,因为我没有引用它。很抱歉我第一次提问时没有表达清楚,现在我已经重新措辞了。感谢迄今为止所有的回答。

我能理解。有很多时候,您会调用带有IDisposable参数的方法,其作用域始于该方法调用并在该方法调用结束。如果它需要5个IDisposable参数,那么该怎么办?嵌套使用语句5层深?在try..finally中放置5个实例化和5个Dispose?丑陋。 - mbeckish
9个回答

5

"如果我无法显式调用Dispose(),那么IDisposable对象会发生什么?"

通常情况下,您可以在所有可处理的对象上调用Dispose(使用using语句隐式调用或显式调用),但是在假设情况下,您无法调用Dispose时,它取决于对象的实现方式。

一般来说,.Net对象将遵循这些线路。该模式是定义一个终结器,在未调用dispose的情况下清除内容,然后让dispose抑制终结器。这样可以减少内存负担并使GC的工作量更少。

调用终结器中Dispose的众多问题之一是,您正在将单线程问题转换为多线程问题,终结器将在不同的线程上运行,这可能会暴露一些非常微妙且难以捕获的错误。此外,这意味着您将长时间持有未管理的资源(例如,您打开一个文件,忘记调用close或dispose,下次要打开它时它被锁定)。

底线是,最好的做法是处理所有可处理对象,否则可能会引入奇怪且复杂的错误。但需要注意的是,某些框架(例如SharePoint)返回不应根据文档处置的对象的共享实例。

我通常发现使用“using”模式处理对象时代码更易读。显式调用Dispose(object.Dispose())的问题在于很难追踪对象的分配位置,并且很容易忘记。您不能忘记using语句的右括号,否则编译器会报错 :)

编辑/注意事项

根据MS文档,您不应调用对GetContextSite返回的SharePoint共享对象的引用进行处置。因此,在此处需要格外小心。

请参见此答案以获取应使用的安全SharePoint模式。

但是,如果您有对共享资源的引用(例如,当Web Part中的对象由GetContextSite方法提供时),请勿使用任一方法关闭该对象。在共享资源的情况下,使用任一方法都会导致出现访问冲突错误。在您拥有对共享资源的引用的情况下,让Windows SharePoint Services或您的门户应用程序管理该对象。


谢谢!到目前为止,我最喜欢你的回答。我应该像引用的方式那样措辞我的问题。我已经投了你一票,但让我再考虑一下。实际上,我会拆分语句,以便能够明确地调用dispose函数。在工作中,我们遇到了很多Sharepoint内存不足的问题。 - barneytron
请注意,非SharePoint开发人员提供的通用指导可能会有误 - 在SharePoint中,规则是不同的,因为某些SPSite/SPWeb对象被认为是由框架“拥有”的,不应该由自定义代码进行处理。dp下面的代码是正确的。 - dahlbyk
我想这个问题应该被分开来讨论...我扩展了我的答案并链接回了你的答案。 - Sam Saffron

4
以下表述更为通顺易懂:

以下内容的表述更为自然流畅:

using (SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb())
{
    SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
}

3

您应该显式地(或通过using语句隐式)调用Dispose方法。将代码拆分为多行的其他原因包括:

  • 可读性更强
  • 更容易调试

Dispose方法可以在终结器中执行,但最好自己调用它。


当然,调用Dispose并不意味着该对象会在那时被GC回收,它只是告诉GC它已经准备好被收集了。 - Jon Limjap
是的,但在这个答案的上下文中,我认为作者更担心的是SPWeb的资源,而不是类实例。 - aku
并非所有的 IDisposable 对象都有终结器;通常会有,但不是必须的。 - Marc Gravell
马克,没错!这就是为什么我说手动调用更安全的原因。 - aku
这是错误的:调用Dispose并不会告诉GC对象已经准备好被回收 - 只有当对象不再可达时才准备好被回收。通常它确实会调用SuppressFinalize,但那是完全不同的事情。 - Joe
那也是不正确的。 Dispose 只执行其方法中所包含的内容,没有多余的部分。 Dispose 用于以受控制的方式尽快释放 .net 托管内存之外的资源,例如数据库连接、非托管内存。然后对象将在 GC 决定时被回收。 - Spence

3

我建议将该行代码分割,并使用Dispose。如果一个对象实现了IDisposable接口,你必须假设它需要被处理并在using块中使用。

using (SPWeb spWeb = SPControl.GetContextSite(HttpContext.Current).OpenWeb())
{
    SpUser spUser = null;
    if (spWeb != null)
    {
        spUser = spWeb.SiteUsers[@"foo\bar"];
    }
}

通过这样做,您可以处理打开外部资源的OpenWeb()调用中的对象释放和错误。


1

正如一些人所说:您决不能处理您未创建的对象。

因此,在您的示例中,您不应该处理SPWeb或SPSite对象!

那会破坏当前的SPRequest对象。它可能看起来仍然在工作,但如果您稍后添加新的Web部件,或尝试打开Web部件的工具窗格,您将遇到各种奇怪的错误。

正如已经提到的,必须处理SPWeb和SPSite的实例,即您自己创建(new)的实例。

可以使用using()或try / finally来完成这项工作(无论如何,使用()语句都会在MSIL代码中显示)。如果您使用try / finally,最佳做法是检查您的SPWeb / SPSite实例是否为null,并首先检查SPWeb,因为SPSite会自动处理您的SPWeb。

另一个需要记住的重要事情是,在循环SPWebCollections(如AllWebs或Webs)时,要在循环过程中处理好子网站的释放。如果有很多子网站,并且您正在运行内存潜力有限的32位硬件上,您可能会非常快地用SPRequest对象填满内存。这将导致性能下降,因为它会导致应用程序池定期回收。
话虽如此,也有一个好习惯,就是不要像代码示例中那样组合调用。这很难阅读,而且如果您正在使用应该处理的SPWeb,则无法处理!这种内存泄漏最难以发现,所以不要这样做;-)

我可以推荐Roger Lamb的博客以获取详细信息: http://blogs.msdn.com/rogerla 此外,在Stefan Goßner的博客上也有一些技术细节: http://blogs.technet.com/stefan_gossner/archive/2008/12/05/disposing-spweb-and-spsite-objects.aspx

希望对你有所帮助。 Anders


1

内存泄漏?不用担心,只要IDisposable的实现遵循类库指南,下一次垃圾回收就会清理它。

然而,这揭示了你代码中的一个错误,因为你没有正确管理你使用的IDisposable实现的生命周期(我在http://www.caspershouse.com/post/A-Better-Implementation-Pattern-for-IDisposable.aspx中详细阐述了这个问题)。你的第二个代码块是一个好的第一步,但如果对SiteUsers的调用失败,你不能保证调用Dispose。

你修改后的代码应该像这样:

// Get the site.
var contextSite = SPControl.GetContextSite(HttpContext.Current);

// Work with the web.
using (SPWeb web = contextSite.OpenWeb())
{
  // Get the user and work with it.
  SPUser spUser = web.SiteUsers[@"foo\bar"];
}

下一次垃圾回收应该清理它。垃圾收集器不会调用Dispose方法->您将获得内存泄漏。 - aku
你把语句断章取义了。我说的是假设它遵循类库指南。如果是这样,那么终结器将调用受保护的Dispose实现来释放资源。 - casperOne
抱歉,我误读了您的帖子。无论如何,最好不要依赖它并手动调用dispose。 - aku
假设组件遵循库指南,那么从某种意义上说它是安全的,因为它将在某个时刻被处理,但它并不是确定性的。手动调用Dispose只有在finally块或using语句(扩展到finally块)中才是一个好主意。 - casperOne

1

这里有很多答案都假设只要最终调用Dispose()就可以了。然而,当使用SPSite和SPWeb时,你肯定希望尽早调用Dispose()。确定何时应该调用Dispose()通常很棘手,但有许多好的参考资料可帮助回答这个问题。

至于为什么会这样,Stefan Goßner在这里提供了一个很好的总结:

每个SPWeb和SPSite对象都持有一个对SPRequest对象的引用,该对象又持有一个对SharePoint COM对象的引用,负责与后端SQL服务器通信。
释放SPWeb对象并不会真正从内存中移除SPWeb对象(实际上,.NET框架不允许以确定的方式从内存中移除任何对象)。但是,它将调用SPWeb对象的某个方法,导致COM对象关闭与SQL服务器的连接并释放其分配的内存。
这意味着自SPRequest对象创建以来,与后端SQL服务器的连接将保持打开状态,直到SPWeb对象被释放。
您的代码示例最佳实践应如下:
SPSite contextSite = SPControl.GetContextSite(HttpContext.Current);
using (SPWeb spWeb = contextSite.OpenWeb())
{
  SPUser spUser = spWeb.SiteUsers[@"foo\bar"];
  // Process spUser
}
// DO NOT use spUser
// DO NOT dispose site from HttpContext

请注意,在父级SPWeb被处理后,不安全使用SPUser、SPList等SP对象。

0

来自http://msdn.microsoft.com/en-us/library/aa973248.aspx最佳实践:使用可处理的Windows SharePoint Services对象

如果SPSite对象是从SPControl.GetContextSite获取的,则调用应用程序不应该处理该对象。因为SPWeb和SPSite对象保持这样派生的内部列表,处理对象可能会导致SharePoint对象模型行为不可预测。


-1
似乎还没有人提到这个:
如果你的对象需要一个销毁器(即拥有必须被释放的资源),那么你应该实现一个终结器来调用销毁器的方法。
在销毁器中,你可以添加以下代码行:
System.GC.SuppressFinalize(this)

如果您调用处理程序,则会防止终结。这样,如果需要,您可以很好地使用对象,但通过终结器保证其自我清理(这也是C#具有终结器的全部原因)。


如果一个具有终结器的对象持有封装在另一个对象中的资源,当第一个对象的终结器运行时,另一个对象很可能已经(1)运行了它的终结器,(2)已经安排好运行它的终结器,或者(3)仍然在其他地方使用。在这些情况下,不应该尝试对其他对象进行任何清理。大多数情况下,如果从终结器中删除所有不应该执行的内容,就什么也不剩了(这意味着终结器根本不应该存在)。 - supercat
如果我有一个对象在本地代码中获得了某些资源,那么这个对象必须拥有一个终结器来确保如果该对象超出范围并且未被处理,则释放本地资源。这就是为什么.NET CLR会在应用程序生成未处理的异常时运行终结器。这不是错误处理代码,而是尝试自我清理的代码。 - Spence
确实,这就是为什么FxCop制定了一条规则,即如果一个对象具有处理器,则必须调用它以确保在可能的情况下抑制终结器并尽快返回资源。 - Spence
你最初的陈述是:“如果你的对象需要一个处理器……那么它应该实现一个终结器来调用处理器的方法。”只有当你的对象持有一个或多个资源时,这才是正确的做法:(1)可以在对象的终结器中有用地清理,(2)不会被其他终结器(如封装资源的对象的终结器)清理。很少情况下两个条件都适用。大多数情况下,终结器会带来更多的伤害而不是好处。 - supercat
具有终结器的对象不应保留对任何不需要进行终结的对象的引用。如果一个复杂的类有一个需要终结的部分,那么该部分应该被分离成自己的较小类;如果大类的对象仅持有对小对象的唯一引用,则当大对象被终结时,小对象也将被终结,但垃圾收集器不会无端地保留不需要进行终结的对象。 - supercat

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