SPWeb.Site,是否应该调用Dispose()方法?

18

更新于 2009 年 06 月 08 日 15:52: 简短回答为不行 原问题:

我找不到关于在SPWeb.Site上处理回收的任何参考资料。我已经阅读了一些更受欢迎的最佳实践文档,其中包括有关处理 SharePoint 对象的:

不幸的是,这些指南都没有提到 SPWeb.Site。为了提供一些背景,我正在编写一个公共扩展 API,该 API 接受一个 SPWeb 作为方法参数,例如:

public static void GetWebPartFromCatalog(this SPWeb web, string webPartName)
{
     ......

     SPSite site = web.Site;
     ......

     **OR** ??

     using (SPSite site = web.Site)
     {
         ....
     }
}

我查看了 SPWeb 的 Close() 方法,该方法被 SPWeb.Dispose() 调用,但其中没有任何内容表明实际的 SPSite 成员字段已被处理。

更新:06/08/2009 13:47

根据 Alex 的建议:

"将其放入循环中运行100次,并使用“Troubleshooting SPSite / SPWeb leaks in WSS v3 and MOSS 2007”中描述的SPRequestStackTrace注册表键来检查您的测试代码是否是问题的来源。"

我在 Web 部件中包含了以下代码:

 for (int i = 0; i < 100; i++)
 {
     using (SPWeb web = SPContext.Current.Site.OpenWeb(""))
     {
            SPSite site = web.Site;
            Debug.WriteLine(site.Url);
     }
 }

在 SharePoint 日志中没有任何内容出现。

虽然我不敢从这个简单的实验中得出真正的结论,但它似乎表明不需要释放 SPWeb.Site。有人能够给出关于这个问题更加权威的答案会很好。

更新:2009年06月08日 14:52 受 Greg 评论的启发,我解析了 m_Site 的赋值情况,结果显示它始终通过内部构造函数传递给 SPWeb。例如,SPWeb.OpenWeb 将 this 传递给 new SPWeb()。因此,我更确定 SPWeb.Site 不应该被释放,如果释放可能会导致问题。


看起来并不清楚。我在Roger Lamb的帖子下留言问了同样的问题,但没有得到回复。 - Alex Angas
在我看来,这只是我的观点,不,你永远不应该处理那个对象。为什么?因为它是从另一个对象的属性返回的。如果那个其他对象是可处理的,你应该处理那个,并让它自己处理资源。你从属性中读取的任何内容都不应该由你自己的代码处理,对我来说,这将构成该框架中的错误。 - Lasse V. Karlsen
1
@lasse-v-karlsen 我完全同意你的观点,但 SharePoint API 中存在许多怪癖,如果不理解这些可能会造成内存泄漏风险。因此,即使它可能是API中的错误,我仍希望知道是否确实存在 :) - Edward Wilde
Keith Dahlby尝试解决SPList.ParentWeb问题的历程:http://solutionizing.net/2008/08/10/is-splistparentweb-a-leak/ - Alex Angas
这里有很好的讨论和实验,很抱歉我错过了! :) - dahlbyk
@Lasse-V-Karlsen 我相信你需要处理的唯一返回SPSite的属性是Microsoft.Office.Server.UserProfiles.UserProfile.PersonalSite。 - dahlbyk
8个回答

8

柯克的答案 是正确的。在创建SPWeb之前,您必须有一个SPSite的句柄,并且当您调用SPWeb.Site时,它是相同的SPSite实例。

让我们思考一下这个问题的含义 - 如果您不控制SPSite的创建,但其子网站之一被外部代码传递给您,而您处置了该站点,当控制返回给调用代码时,您已处置了可能尚未完成的站点!把自己放在调用代码的位置:您将SPWeb传递到一个方法中,当该方法完成时,您正在使用的SPSite已关闭。始终由实例化者负责清理他们分配的资源。在这种情况下不要处理SPSite。


按照同样的理论,SPLimitedWebPartManager也应该清理,但它没有!http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx#SPDisposeCheckID_160 这是为什么呢?还是API中的另一个不一致之处? - Alex Angas
@Alex,我不认为这是一样的。在你提供的例子中,SPSite和SPWeb仍然需要被显式清理,以及由SPLimitedWebPartManager实例化的其他 Web。 - Rex M
非常好的表述,Rex。你应该始终将SPWeb视为“属于”SPSite,而不是相反。特别是因为过早地处理SPSite将首先调用其创建的所有SPWeb上的Dispose()方法。因此,我总是假定创建SPSite的代码将对其进行处理。(*维基文章记录了所有已知需要清理SPSite的情况。) - dahlbyk
我想我现在明白了。如果我创建SPLimitedWebPartManager,那么清理它及其关联的Web属性就是我的工作。我的疑问是API没有为我设计这样做。我想再问一个问题... - Alex Angas
1
我怀疑 LWPM 并非出于设计意图泄漏 SPWeb。这可能只是被忽略了,并且永远不会被修复,因为那可能会破坏清除泄漏的代码。 - dahlbyk

5

就我个人而言(有时候很危险),我的想法是...

似乎你不能没有一个SPSite而有一个SPWeb。所以,如果你获得了SPWeb但并没有通过新的SPSite或已有的SPSite提供给你的方式获取它,则可能无需担心处理SPSite。

这只是一种猜测。好问题!


问题在于,由于这是SPWeb的扩展方法,调用者可能已经创建了SPSite对象。然而,我没有对SPSite对象的引用,因此需要使用SPWeb.Site。这就引出了一个问题,我需要自己清理吗 :)我可以要求调用者包含SPSite(将其添加到参数列表中),但我不想增加该方法的参数数目。 - Edward Wilde

2

Reflector告诉我们,当您调用Site属性时,这是在SPWeb对象内部运行的代码:

public SPSite Site
{
    get
    {
        return this.m_Site;
    }
}

它并没有创建一个新的SPSite对象,只是返回它已经拥有的那个对象,如果需要的话,这将由SPWeb.Dispose()来处理。因此,您可以安全地使用它,并且我建议避免对其进行处理,以免SPWeb依赖关系出现问题。


2
我猜这取决于m_Site是如何被分配的。使用反射分析来显示m_Site上的“assigned by”显示它是在私有成员SPWebConstructor()中分配的,该构造函数通过SPWebs内部构造函数列表传递了site...因此,最终SPWeb从未新建一个SPSite。 - Edward Wilde

2
这段内容不太清楚。有篇名为“Disposing SPWeb and SPSite Objects”的Stefan博客指出,“您需要确保只处置代码所拥有的SPSite和SPWeb对象”。然后,这个来自Microsoft的线程由Michael Washam表示,这个模式会泄漏。
除非您能找到另一个参考或其他人知道,否则为什么不在开发服务器中测试它,并将您的结果作为此问题的答案添加? 将其放入运行100次的循环中,并使用Troubleshooting SPSite/SPWeb leaks in WSS v3 and MOSS 2007中描述的SPRequestStackTrace注册表键来检查您的测试代码是否是问题的根源。

好的建议,我会尝试一下并发布结果。 - Edward Wilde
1
引用的 MSDN 线程使用了反模式,从一个辅助方法返回了一个 SPWeb 引用,并在此过程中犯下了更严重的错误,即新建了一个 SPSite。相反,该代码应重构为消耗 SPSite/SPWeb 参数的方法:http://solutionizing.net/2009/01/06/elegant-spsite-elevation/ - dahlbyk

1

在我的SharePoint代码中,我经常使用这种模式作为一个经验法则,如果调用depose不会导致任何崩溃,我就调用它。我遵循的另一个规则是,我尽量不创建不会导致SPRequest对象净增益的扩展(SPRequest对象是与所有重量级com对象通信的.net对象)

现在来分解你的例子

 for (int i = 0; i < 100; i++)
 {
     using (SPWeb web = SPContext.Current.Site.OpenWeb(""))
     {
            SPSite site = web.Site;
            Debug.WriteLine(site.Url);
     }
 }

关键在于SPContext.Current.Site。由于我们知道SPContext会正确地清理自己(这是为数不多的几个对象之一),因此我们可以确保网站已被正确清理,但是这远非您问题的正确答案。(请注意,您应该泄漏通过OpenWeb获取的网站)
public static void GetWebPartFromCatalog(this SPWeb web, string webPartName)

你需要获取SPSite对象来处理网页部件。

  1. 如果网页被释放,web.Site属性确实会自我清理。
  2. 为什么不直接传递SPSite对象,让函数的使用者自己担心呢?在大多数情况下,他们应该会使用SPContext.Current.Site进行调用。
  3. 大多数时候我需要担心权限问题,我们的dll并不都在GAC中,因此当我编写新的扩展方法时,我最终需要将其包装在SPSecurity.CodeToRunElevated中。

考虑到这些因素,我最终会将其编写为...现在Dispose检查将会在这种情况下触发,因为传递的SPSite作为参数,无法跟踪它在何处被释放。

public static void GetWebPartFromCatalog(this SPSite site, string webPartName) {
     SPSecurity.CodeToRunElevated( () => {
         using(SPSite suSite = new SPSite(site.Id)){
             //do what you need to do
         }
     };
}

我认为你指的是SPSecurity.RunWithElevatedPrivileges - CodeToRunElevated是委托类型。话虽如此,在帮助程序中默认提升权限似乎不是一个好主意。如果您确实需要提升权限,最好使用扮演身份:http://solutionizing.net/2009/01/06/elegant-spsite-elevation/ - dahlbyk

1

你尝试过使用SPDisposeCheck检查你的程序集吗?也许它会给你一些处理问题的提示。


1
SPDisposeCheck 只验证“最佳实践”页面上的那些错误,我认为。 - Alex Angas

1

0

如果它是一个新对象,你需要删除 SPSite / SPWeb 实例。

例如:

using(SPSite site = new SPSite("url"))
{
    DoSomething;
}

在这里,使用将负责删除对象,但是如果您从SPContext.Current获取了对象的引用,则不应显式处理该对象,因为引用应该从SPContext.Current中可用。


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