Udostępnij za pośrednictwem


try / finally using() SharePoint Dispose()

Chances are that by now if you have been developing on the Microsoft SharePoint Office Server (MOSS) 2007 and Windows SharePoint Services (WSS) 3.0 Object Model (OM) for any length of time you are aware of the importance of when to (and when not to) call Dispose()  on your SharePoint Objects.  However, you may not be aware that if you do not always wrap your Dispose()  method(s) within a finally { } block either implicitly by implementing the using() { } statement or by explicitly wrapping it in your code with try/finally then you you can’t guarantee that your intended objects Dispose() will ever be called during an exception. 

SharePoint developers are required to be alert and stay on their toes when creating and releasing SPSite and SPWeb objects.  Intimately understand your applications code path(s) and object scope lifetime in order to optimize performance and good memory hygiene with the SharePoint platform is paramount.  Every .NET application (not just SharePoint) must play by the .NET Common Language Runtime (CLR) house rules to ensure that Dispose()  gets called (when appropriate) in a timely fashion if you want to avoid very serious negative consequences exacerbated in heavily utilized production SharePoint environments.  You need to make certain and take extra measures to take that code snippet you found which for brevity excluded proper dispose methods and works fine on a developers Virtual PC environment later keeps the business stakeholders up late at night wondering why their customized SharePoint site is having production problems under load.

Below I discuss some edge cases that you should be aware of when implementing using() and try/finally in your code to help mitigate the risk of memory leaks and help tighten up your production code.

Understanding the using() statement

Since you have your SharePoint OM homework you know that you can take advantage of the using() statement to increase the readability and help simplify the the amount of code you have to write to properly release the unmanaged memory held onto by the SharePoint managed code.  Behind the scenes .NET has the CLR automatically inject into your MSIL code a try/finally block (notice I did not say try/catch/finally) and the Dispose()  method is getting called on your behalf. 

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

Coding with try/finally

What might not be so commonly known when writing SharePoint code is that there are some cases where we should not implement using() .   We do have the option of manually emulating what the CLR does behind the scenes automatically for us by implementing try/finally blocks which contain the respective Dispose() method(s) which ultimately allows our SharePoint IT Administrator(s) to get their much needed beauty sleep.  By the way, there is nothing wrong with adding a try/catch/finally as long as you make certain that you are properly handling the exceptions in the catch { } block and not just eating them without some sort of logging in place.  You do not want to see any empty catch { } blocks in your production code.  Also, note the best practice of conditionally testing for null prior to disposing as shown in the following code. 

void TryFinallyBestPractice()
{
    SPSite siteCollection = null;
    SPWeb web = null;

    try
    {
        siteCollection = new SPSite("https://moss");
        web = siteCollection.OpenWeb();
        Console.WriteLine(web.Title);
    }  // optionally catch { ... make sure you handle if you use catch }
    finally
    {
        if (web != null)
            web.Dispose();

        if (siteCollection != null)
            siteCollection.Dispose();
    }
}

Don’t use SPContext with using()

The following example illustrates a common bad practice of wrapping a SPContext originated SPWeb object in a using() statement which as we now know will automatically inject a try/finally that will call web.Dispose() .  In cases where RootWeb equals the SPContext “web” as shown below this can cause production problems since SPContext objects are managed by SharePoint itself and should not be released by you (ever).

 // Bad.. Dispose() is automatically called on web which SPContext equality
using( SPWeb web = SPControl.GetContextWeb(HttpContext.Current)) { ... }
Combining OM Calls

Use caution when combining SharePoint Object Model calls into the same line as these can be some of the most tricky leaks to find.  Dissecting the following example we see:

  • SPContext.Current.Web.Url is fine no dispose needed
  • SPSite is instantiated which needs to be Disposed but is lost since we are also calling OpenWeb() which is actually what using() will Dispose()
 void CombiningCallsLeak()
{
    using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb())
    {
        // ... new SPSite will be leaked
    } // SPWeb object web.Dispose() automatically called
}

You would have to split the previous sample up into nested using() statements to avoid leaking memory:

 void CombiningCallsBestPractice()
{
    using (SPSite siteCollection = new SPSite(SPContext.Current.Web.Url))
    {
        using (SPWeb web = siteCollection.OpenWeb())
        {
        } // SPWeb object web.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called
}
foreach statement and try/finally

Unfortunately foreach can’t implement using() so extra measures must be taken to make sure your code makes it to the intended Dispose by using the following try/finally pattern in your code.  Use extra caution with any code that is part of a iteration such as foreach because they have a way of turning a small problem in dev into a big problem in production especially when called with navigation or on SharePoint environments with lots of sites.

public void TryFinallyBestPractice()
{
    using (SPSite siteCollection = new SPSite("https://moss"))
    {
        using (SPWeb outerWeb = siteCollection.OpenWeb())
        {
            foreach (SPWeb innerWeb in outerWeb.Webs)
            {
                try //should be 1st statement after foreach
{
// ... something evil occurs here
}
finally
{
if(innerWeb != null)

                        innerWeb.Dispose();
                }
            }
        } // SPWeb object outerWeb.Dispose() automatically called
    }  // SPSite object siteCollection.Dispose() automatically called
}

SharePoint Memory Internals

If you’ve made it this far without falling asleep you might be interested in learning more about the SharePoint internals of memory management and why it’s so important for you (the owner of the application code) to know when is the optimal time for performance reasons to hold and release your SharePoint objects.  As SharePoint Application developers you are familiar with the public SPSite and SPWeb objects but internally to get their work done the real managed object that holds onto the unmanaged heap is SPRequestInternalClass which is internal to a wrapper class SPRequest.

Internally we use a RCW (runtime callable wrapper) which is essentially a finalizable object (there are subtle differences but they don’t really come into play here).    If that object is no longer rooted, the finalizer (teardown of the RCW) will release the native memory.   However, like normal finalizers the RCW teardown is performed by a single finalizer thread which generally can’t keep up with cleaning these objects if they’re being leaked many times per second.

Not releasing the SharePoint objects in a timely fashion can lead to poor memory hygiene including excessive fragmentation, pinning, and OOM exceptions building very quickly.  Problematic code which fails to properly dispose objects in a timely fashion becomes exacerbated especially in x32bit environments with a large number of sites.  We encourage all Enterprise SharePoint farms to use x64bit versions of the OS and MOSS to take advantage of the additional addressable memory.

Resources other than just memory are effected by not properly disposing memory.  For example, SQL Server establishes a 1:1 connection with each SPRequest object and it lives up to the release of the internal SPRequest object (which is used by your SPSite and SPWeb object).

If you’d like to read more perspective on SharePoint internals and also discover ways to look for evidence of dispose related memory leaks in the ULS logs I’d encourage you to read Stefan Goßner’s excellent blog on Disposing SPWeb and SPSite objects .

Special thanks to my colleagues at Microsoft Dave Aknai, Paul Andrew, Todd Carter, Shawn Cicoria, Stefan Goßner, Scott Harris, Vishwas Kulkarni, Zach Kramer, Randy Thomson, and Sean Thompson.

Comments

  • Anonymous
    January 14, 2009
    Note that SPSite.Dispose() automatically disposes all open child SPWebs. This has two consequences:
  1. In your first try/finally example, the call to web.Dispose() should either come before siteCollection.Dispose() or be left out altogether.
  2. The common pattern of directly nested usings for SPSite and SPWeb just introduces unnecessary try/finally overhead. The only reason to wrap an SPWeb in using is if additional work will be done with the SPSite after the SPWeb is no longer needed, as in the case of enumerating over AllWebs.
  • Anonymous
    January 14, 2009
    Hi Keith, thanks for your feedback.  On your first comment the order is now shown correctly.  Removing web.Dispose() while technically possible is not our recommended guidance.  The same amount of work will be done by you or by SPSite owner but this allows you control over when it gets done. The overhead for try/catch/finally is minimal, it's the exception being thrown that is expensive.  AllWebs is a good example of needing to call Dispose on the web during the iterations of the collection (especially with large # of sites) because the cleanup by SPSite owner does not happen until after the exit of the AllWebs foreach loop and after in your example the owner SPSite has been disposed.  By this time it's too late as we've had enterprise sized farms get OOM exceptions within the iteration of AllWebs with a large number webs because the memory for each web will be aggregated.

  • Anonymous
    January 14, 2009
    If you’re doing WSS/MOSS development then you need to understand the implications of when to Dispose

  • Anonymous
    January 14, 2009
    If you’re doing WSS/MOSS development then you need to understand the implications of when to Dispose

  • Anonymous
    January 18, 2009
    Last Updated: January 18, 2009 Overview Windows SharePoint Services (WSS 3.0) and Microsoft Office SharePoint

  • Anonymous
    January 22, 2009
    To Dispose Or Not To Dispose....That Is The Question

  • Anonymous
    February 10, 2009
    First of all, thanks for your excellent post about best practices in sharepoint development. They are very, very helpful. I just have a little doubt that I wanted to share with you. I have been developing an SPItemEventReceiver in sharepoint, and I have try to follow your guidelines and those expressed in the chapter "Using objects in event receivers" http://msdn.microsoft.com/en-us/library/bb687949.aspx, that advised us of not disposing or implementing the "using" statement, with the SPSite and SPWeb objects obtained using the SPItemEventProperties because they are disposed by the system, I guess in the same way that happens with those obtained with SPContext The code I use in the SPItemEventReceiver is the following, which do not use try/catch/dispose or a using statement. public override void ItemAdding(SPItemEventProperties properties) {          site = properties.OpenWeb();                              siteColl = site.Site; } My question is the following: In that same solution, I have implemented a SPFeatureReceiver to register by code the previous event receiver with a specific list. Here I obtained SPWeb object using the following code: public override void FeatureActivated(SPFeatureReceiverProperties properties) {          SPWeb site = (SPWeb)properties.Feature.Parent; } I think, but I'm not sure, that it would be a bad practice to dispose the SPWeb object (or enclose it in a "using" statement), obtained thru this manner, because I'm obtaining the SPWeb object in a similar manner that when I use  

  • properties.OpenWeb(); in a SPItemEventReceiver
  • SPContext.GetContextWeb(HttpContext.Current) in other situations and I am not instantiating the SPWeb object with the "new" clause in my code. So I think that I should be left to be dispose by the system, instead of me. I have used de SPDisposeCheck tool and the code passed it without a warning. What do you think it's the correct dispose policy towards the SPWeb objects obtained thru properties.Feature.Parent? Thanks in advance for your help. Angel
  • Anonymous
    February 19, 2010
    My questions is: in the example UsingBestPractice(), if an exception occurs in the inner using{} block, are the objects disposed of or not? If they are not, then the inner using{} block needs to contain a try/catch?

  • Anonymous
    February 20, 2010
    If only a web object is required, is there anything wrong with? using (SPWeb web = SPContext.Current.Site.OpenWeb(SPContext.Current.Web.Url)) {…etc.} OR using (SPWeb web = SPContext.Current.Site.OpenWeb(SPContext.Current.Web.ID)) {…etc.} As an equivalent for: using (SPSite siteCollection = new SPSite(SPContext.Current.Web.Url))       using (SPWeb web = siteCollection.OpenWeb())       {...etc.} Thanks

  • Anonymous
    October 24, 2010
    Hi, i have big doubt about using using SPContext.Current.Web.Url. You mentioned that no need to dispose SPContext.Current.Web.Url. but in your example you mentioned that we can dispose like this using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb()) is this correct?? or we have to use like this using (SPSite siteCollection = new SPSite("http://moss")) ?

  • Anonymous
    August 23, 2011
    Chek here too www.fewlines4biju.com/.../disposing-sharepoint-objects.html

  • Anonymous
    June 18, 2012
    Unfortunately, none of this helps if there is no custom development and the errors are coming from SharePoint itself.