手动释放spsite和spweb

from :Roger Lamb

http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx

Overview

Windows SharePoint Services (WSS 3.0) and Microsoft Office SharePoint Server (MOSS 2007) have some gotchas with serious implications that every SharePoint application developer needs to be intimately familiar with before deploying into production MOSS 2007 farms.  In particular, Microsoft.SharePoint.SPSite , Microsoft.SharePoint.SPWeb , and the often overlooked (MOSS 2007) Microsoft.SharePoint.Publishing.PublishingWeb objects need to be carefully examined and disposed of properly in order to avoid potential memory leaks.

My objective with this blog post was to consolidate several sources compiled by Microsoft's top subject matter experts into a simplified quick reference format which can be used to increase awareness as well as jump start into a SharePoint custom code review. Look for a follow-up post where I will do a high level walk-through using the windows debugger (windbg) to help identify common SharePoint memory leaks.

Note: This Blog will be updated periodically as new dispose leak guidance are released.

Why Dispose?

SPSite and SPWeb classes both implement the IDisposable interface.  Microsoft .NET requires objects that implement the IDisposable interface to properly cleanup the unmanaged resources by explicitly calling the Dispose() method when you are finished using them.  Internally, SPSite and SPWeb both hold references to an "internal class Microsoft.SharePoint.Library.SPRequest" which holds on to unmanaged COM resources.  The consequence of not explicitly disposing unmanaged resources in a timely fashion can lead to not having enough memory for further allocations and quickly consumes memory. Under the hood, when reviewing dump files we see the that the managed objects used by SPSite and SPWeb are relatively small and it's the unmanaged resources that are most concerning and account for approximately 1MB to 2MB for each object instance!  Omitting to explicitly call Dispose() means the .NET (non-deterministic) garbage collector gets out of sync with the finalizer and the unmanaged memory does not get reclaimed in a timely manner possibly blocking future memory allocations. For further reading I recommend reviewing Stefan Goßner's blog Dealing with Memory Pressure problems in MOSS/WSS .

The unmanaged memory leaks can grow very quickly especially when traversing through frequently called areas like site navigation code and item event receivers.  The lack of proper Dispose() hygiene can increase your risk of frequent IIS Application Domain recycles (see Steve Sheppard's blog Overlapped Recycling And SharePoint: Why SharePoint Requires It), Out Of Memory (OOM) exceptions, high memory consumption, and poor performing SharePoint production environments.

To Dispose or not Dispose?! That is the question...

To make matters more confusing for SharePoint developers there are times when SPSite and SPWeb objects should not be disposed and are cleaned up by SharePoint and ASP.NET after page processing is completed.  In addition, there are cases when developers indirectly call a property on a object that creates and holds an internal reference to a SPSite or SPWeb object (for example SPSite.ParentWeb property).  Understanding the origin and the scope the object was created is paramount when determining whether or not to explicitly call dispose.

Dispose Patterns

When writing customized SharePoint code you need to be aware of the scope and context of each SPSite, SPWeb, and PublishingWeb objects lifetime.  When objects are created and destroyed in the same method or iteration scope (foreach or do/while loop) they are the easiest to clean handle.  Things become more complex to review when developers create objects in one method and dispose in another.  Areas to be aware of are assigning objects to class variables or static/global variables which may hold on to the object reference across method calls.

With that said, below are the Dispose most common patterns you need to be aware of when developing customized SharePoint code.

Microsoft.SharePoint.SPList.BreakRoleInheritance (Added 4/11/08)


void SPListBreakRoleInheritanceLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPList list = web.Lists["ListName"];
            list.BreakRoleInheritance(true);
            // SPWeb list.ParentWeb leaked here, requires call to list.ParentWeb.Dispose()
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void SPListBreakRoleInheritanceBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPList list = web.Lists["ListName"];
            list.BreakRoleInheritance(true);
            list.ParentWeb.Dispose(); // Best practice 
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager


void SPLimitedWebPartManagerLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPFile page = web.GetFile("Source_Folder_Name/Source_Page");
            SPLimitedWebPartManager webPartManager =
                page.GetLimitedWebPartManager(PersonalizationScope.Shared);
            // SPWeb object webPartManager.Web leaked
        } // SPWeb object web.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void SPLimitedWebPartManagerBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            SPFile page = web.GetFile("Source_Folder_Name/Source_Page");
            using (SPLimitedWebPartManager webPartManager = 
                page.GetLimitedWebPartManager(PersonalizationScope.Shared))
            {
                webPartManager.Web.Dispose();
            }
        } // SPWeb object web.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

Microsoft.SharePoint.Publishing.PublishingWeb (MOSS 2007 Only)


void PublishingWebCollectionLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web);

            PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs();
            foreach (PublishingWeb innerPubWeb in pubWebCollection)
            {
                // innerPubWeb leak
            }
        } // SPWeb object web.Dispose() automatically called
    } // SPSite object siteCollection.Dispose() automatically called
}

void PublishingWebCollectionBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            PublishingWeb outerPubWeb = PublishingWeb.GetPublishingWeb(web);
            PublishingWebCollection pubWebCollection = outerPubWeb.GetPublishingWebs();
            foreach (PublishingWeb innerPubWeb in pubWebCollection)
            {
                // innerPubWeb referenced so we must call outerPubWeb.Close()
                // otherwise, we will leak each and every innerPubWeb object
                innerPubWeb.Close();
            }
        }  // SPWeb object web.Dispose() automatically called
    } // SPSite object siteCollection.Dispose() automatically called
}

void GetPublishingWebBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
            // As long as you are not using the collection you don't have
            // to call singlePubWeb.Close() explicitly
            PublishingWeb singlePubWeb = PublishingWeb.GetPublishingWeb(web);
        } // SPWeb object web.Dispose() automatically called
    } // SPSite object siteCollection.Dispose() automatically called
}

Microsoft.SharePoint.Publishing.PublishingWebCollection (MOSS 2007 Only)


  • When using the PublishingWebCollection class GetPublishingWebs().Add() method you are required to call close on the newly added PublishingWeb object.  For a code sample refer to the MOSS SDK GetPublishingWebs() sample.

Microsoft.SharePoint.SPSite


  • new SPSite() - Instantiating SPSite objects with the new operator needs to be disposed.

Note: With C# you can automatically have the Dispose() called for you when the object leaves the scope by wrapping the code with the using() { } statement.

void CreatingSPSiteLeak()
{
    SPSite siteCollection = new SPSite("http://moss");
    // siteCollection leaked
}

void CreatingSPSiteExplicitDisposeBestPractice()
{
    SPSite siteCollection = new SPSite("http://moss");
    siteCollection.Dispose();
}

void CreatingSPSiteWithAutomaticDisposeBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
    } // SPSite object siteCollection.Dispose() automatically called
}

Avoid the following pattern which collapses SPSite and SPWeb calls.  The example returns the SPWeb site object wrapped by a using statement which gets disposed but there is no way to dispose the underlying SPSite object.

void OpenWebLeak()
{
    using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb())
    {
        // SPSite leaked !
    } // SPWeb object web.Dispose() automatically called
}

void OpenWebBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
        } // SPWeb object web.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called
}

 

  • Updated 10/4/08 RootWeb property guidance updated here - Do not explicitly call Dispose() on the SPSite.RootWeb property as previously indicated in the Whitepaper Best Practices: Using Disposable Windows SharePoint Services Objects .  Note that the owning SPSite object must be properly Disposed (or not Disposed in the case of SPContext) as described by the rules listed elsewhere in this blog.

 

  • Updated 10/5/08 Properties LockIssue, Owner, and SecondaryContact internally used the RootWeb property.  Based on the updated guidance for RootWeb no explicit Dispose on any of these properties are required.  Note that the owning SPSite object must be properly Disposed (or not Disposed in the case of SPContext) as described by the rules listed elsewhere in this blog.

 

  • AllWebs[] Indexer returns SPWeb object that needs to be disposed to avoid aggregation of memory which can lead to memory pressure when running on a site collection with large number of sub sites.
void AllWebsForEachLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in siteCollection.AllWebs)
            {
                innerWeb.Dispose();   // Explicit Dispose must be called to avoid aggregation of memory
            }
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void AllWebsForEachBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in siteCollection.AllWebs)
            {
                innerWeb.Dispose();
            }
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void AllWebsIndexerLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        SPWeb web = siteCollection.AllWebs[0];
        // SPWeb web leaked
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void AllWebsIndexerBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.AllWebs[0])
        {
        } // SPWeb object web.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}
void AllWebsAddLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        SPWeb web = siteCollection.AllWebs.Add("site-relative URL");
        // SPWeb web Leaked
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void AllWebsAddBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb web = siteCollection.AllWebs.Add("site-relative URL"))
        {
        } // SPWeb object web.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

Microsoft.SharePoint.SPWeb


void ParentWebLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            SPWeb parentWeb = outerWeb.ParentWeb; // Internal reference to SPWeb parentWeb
            string sTitle = parentWeb.Title; 
            string sUrl = parentWeb.Url; 
            // SPWeb object parentWeb leaked
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void ParentWebBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            using (SPWeb parentWeb = outerWeb.ParentWeb) // Internal reference to SPWeb parentWeb
            {
                string sTitle = parentWeb.Title;
                string sUrl = parentWeb.Url;
            } // SPWeb object parentWeb.Dispose() automatically called
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}
void WebsLeak()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in outerWeb.Webs)
            {
                // SPWeb innerWeb leak
            }
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

void WebsBestPractice()
{
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in outerWeb.Webs)
            {
                innerWeb.Dispose();
            }
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called 
}

Microsoft.SharePoint.WebControls.SPControl

void SPControlBADPractice()
{
    SPSite siteCollection = SPControl.GetContextSite(Context);
    siteCollection.Dispose();   // DO NOT DO THIS
    SPWeb web = SPControl.GetContextWeb(Context);
    web.Dispose();  // DO NOT DO THIS
}

void SPControlBestPractice()
{
    SPSite siteCollection = SPControl.GetContextSite(Context);
    SPWeb web = SPControl.GetContextWeb(Context);
    // Do NOT call Dispose()
}

Microsoft.SharePoint.SPContext

void SPContextBADPractice()
{
    SPSite siteCollection = SPContext.Current.Site;
    siteCollection.Dispose(); // DO NOT DO THIS
    SPWeb web = SPContext.Current.Web;
    web.Dispose(); // DO NOT DO THIS
}

void SPContextBestPractice()
{
    SPSite siteCollection = SPContext.Current.Site;
    SPWeb web = SPContext.Current.Web;
    // Do NOT call Dispose()
}

Microsoft.SharePoint.Administration.SPSiteCollection

void SPSiteCollectionIndexerLeak()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;

    SPSite siteCollection = siteCollections[0];
    // SPSite siteCollection leak 
}

void SPSiteCollectionIndexerBestPractice()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;

    SPSite siteCollection = siteCollections[0];
    siteCollection.Dispose();
}

void SPSiteCollectionForEachLeak()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;

    foreach (SPSite siteCollection in siteCollections)
    {
        // SPSite siteCollection leak
    }
}

void SPSiteCollectionForEachBestPractice()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;

    foreach (SPSite siteCollection in siteCollections)
    {
        siteCollection.Dispose();
    }
}
  • Add() returns a SPSite object which needs to be disposed.
void SPSiteCollectionAddLeak()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;
    SPSite siteCollection = siteCollections.Add("sites/myNewSiteCollection",
        "DOMAIN\\User", "roger.lamb@litwareinc.com");
    // SPSite siteCollection leak
}

void SPSiteCollectionAddBestPractice()
{
    SPWebApplication webApp = new SPSite("http://moss").WebApplication;
    SPSiteCollection siteCollections = webApp.Sites;
    using (SPSite siteCollection = siteCollections.Add("sites/myNewSiteCollection",
        "DOMAIN\\User", "roger.lamb@litwareinc.com"))
    {
    } // SPSite object siteCollection.Dispose() automatically called
}

Microsoft.SharePoint.Portal.SiteData.Area.Web (Obsolete)


  • Area.Web property returns a SPWeb object which will need to be disposed.  Although classes Area and AreaManager are now obsolete in MOSS 2007 it is still of concern when migrating legacy code.
void AreaWebLeak()
{
    // AreaManager and Area are obsolete in MOSS but this should still be noted
    Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}"));
    string str = area.Web.Title;
    // SPWeb area.Web leak
}

void AreaWebBestPractice()
{
    // AreaManager and Area are obsolete in MOSS but this should still be noted
    Area area = AreaManager.GetArea(PortalContext.Current, new Guid("{GUID}"));
    string str = area.Web.Title;
    area.Web.Dispose();
}

Cross Method Dispose Patterns

  • The following example demonstrates the common practice of holding onto the SPSite and SPWeb objects across methods in a class.  There are times where this design pattern is required however make sure you don't overlook the appropriate time to call dispose when you are finished with the cross method calls.  Below is an example of this pattern and shows and example of a leak of both SPSite and SPWeb when the class is torn down.
public class CrossMethodLeak
{
    private SPSite _siteCollection = null;
    private SPWeb _web = null;

    public void MethodA()
    {
        _siteCollection = new SPSite("http://moss");
        _web = _siteCollection.OpenWeb();
    }

    public void MethodB()
    {
        if (_web != null)
        {
            string title = _web.Title;
        }
    }

    public void MethodC()
    {
        if (_web != null)
        {
            string name = _web.Name;
        }
    }
}

Summary

If you have extended MOSS 2007 or WSS 3.0 with custom code you should digest this post carefully to avoid expensive consequences commonly found in production environments.  For a more verbose explanation of many of the topics covered in this blog post I suggest you read Scott Harris's MSDN White Papers Best Practices: Using Disposable Windows SharePoint Services Objects and Best Practices: Common Coding Issues When Using the SharePoint Object Model .

Special thanks to my Microsoft colleagues Scott Harris, Stefan Goßner, Steve Sheppard, Sean Thompson, Lisa Guthrie, Cliff Green, and Rick Caudle for reviewing and helping contribute to this blog post.

 

下边文章会说明删除Context得到的对象时候发生的问题.

关键点:当Context.Current.Web is RootWeb时候,context.Site.RootWeb 就等于 Context.Current.Web

We've been working on this one for a while, and it's been driving us nuts...

In the current project we're working on, we haven't been able to add any new web parts to pages, nor have we been able to edit the properties on the existing ones.  When we do, we've been seeing the three following errors:

- Cannot save the property settings for this Web Part. Exception occurred. (Exception from HRESULT: 0x80020009 (DISP_E_EXCEPTION))
- Unable to add selected web part(s). Exception occurred. (Exception from HRESULT: 0x80020009 (DISP_E_EXCEPTION))
- Unable to add selected web part(s). List View Web Part could not be added, list may be hidden.

Even stranger was that it was only occurring in the root web of the current site collection.

In researching the 0x80020008 error when related to web parts, the few people that have found anything have said that it could be caused by disposal of the SPContext.Current.Site or SPContect.Current.Web objects.  We looked all over our code and didn't find that we were doing that anywhere.

We switched the master page back to the default.master, and we could add and edit web parts without any issues, so we then started whittling down the server controls on the custom master page until we found three custom server controls that were present when the web part errors occur.  Taking a closer look, these three controls all had the following lines in common.

using (SPWeb rootWeb = SPContext.Current.Site.RootWeb)
{
    //code
}

Commenting out of the code caused the errors to go away.  What's going on here? 

Turns out that there's something that the Best Practices: Using Disposable Windows SharePoint Services Objects post and Roger Lamb's SharePoint 2007 and WSS 3.0 Dispose Patterns by Example should point out a little clearer.  If you look at the bottom of Roger's post, there's a comment by Stephen Kaye where he hits the nail on the head:

If your current context’s web is the root web of you current context’s site then the RootWeb property of the site will reference the same object return by SPControl.GetContextWeb and SPContext.Current.Web and should therefore not be disposed.

So, this means that if you are in the root web of your site collection, and you dispose of SPContext.Current.Site.RootWeb, you're actually disposing of SPContext.Current.Web.  Not good.

Here's how we changed the code to ensure that we could use the RootWeb SPWeb object, but not dispose it if the SPContext.Current.Web is the RootWeb:

SPWeb rootWeb = null;

if (SPContext.Current.Web.IsRootWeb)
    rootWeb = SPContext.Current.Web;
else
    rootWeb = SPContext.Current.Site.RootWeb;

//code

if (!SPContext.Current.Web.IsRootWeb)
    rootWeb.Dispose();
posted @ 2008-11-17 18:41  彷徨......  阅读(1015)  评论(1编辑  收藏  举报