Monday, 3 October 2016

Disposing objects instantiated by MEF

Experienced developers that has worked with the official extensibility framework in .NET, the Managed Extensibility Framework (MEF) allows the composition of different parts into more composite parts through composition. MEF has got similarities to other IoC framework, where you register components and then make use of them in other components. However, with MEF there is a caveat and an important one too!
MEF beautifully abstracts away the IoC container and lets you specify parts that can be epxorted and imported. But if you inspect your application or system with a memory profiler such as JetBrains DotMemory or Red Gate Memory Profiler, you soon find out that much of the memory used by your applications is not properly disposed, i.e freed up after use. This is the case for nonshared (non-singleton) objects that are exported and then instantiated (imported). This means that your application will through continued use hold more and more memory. It will leak memory. By inspecting the memory dependency chain, one can see that MEF is the reason why the nonshared objects instantiated by MEF is not released, even after the objects are issued to be disposed.

I use in this code the ServiceLocator found with the Enterprise Library. Make note that my code will break up the dependency chain that hinders the object, but it does not mean that necessarily objects will be disposed right away. After all, .NET is managed and decides itself when objects are really to be disposed. But if you strive with releasing objects that are tied to memory even after use and also use MEF, read on.

I use the Factory pattern here to instantiate objects. I also use the new feature in .NET 4.5 that is called the ExportLifeTimeContext. I also use the ExportFactory in MEF inside a class called ExportFactoryInstantiator that does actual instantiation of the objects and keeping a track of these ExportLifeTimeContext objects. As noted, you need at least .NET 4.5 to make this work. For .NET 4.0 users, sorry - you are out of luck as far as I know. Upgrade your application to .NET 4.5 if possible and get the newer version of MEF.

The code below shows how you can accomplish control over memory resources using MEF:

MefFactory.cs

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.Practices.ServiceLocation;

namespace SomeAcme.Client.Infrastructure.IoC
{

    /// <summary>
    /// Factory for MEF Parts that is able to actually dispose MEF instantiated objects and get around bug in MEF 
    /// where objects never gets properly GC-ed when they should dispose
    /// </summary>
    /// <typeparam name="T"></typeparam>
    [Export]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class MefFactory<T> : IPartImportsSatisfiedNotification 
    {

        /// <summary>
        /// Backlog that keeps track of mef parts that are instantiated via this factory 
        /// </summary>
        private static readonly ConcurrentBag<ExportLifetimeContext<T>> MefParts = new ConcurrentBag<ExportLifetimeContext<T>>();

        /// <summary>
        /// Disposes parts added to the mef factory backlog of type T
        /// </summary>
        public static void DisposeMefParts()
        {
            ExportLifetimeContext<T> item;
            while (MefParts.TryTake(out item))
            {
                if (item != null)
                    item.Dispose();
            }
        }

        /// <summary>
        /// Disposes parts added to the mef factory backlog of type T by a given predicate condition
        /// </summary>
        public static void DisposeMefParts(Predicate<T> condition)
        {
            ExportLifetimeContext<T> item;
            List<ExportLifetimeContext<T>> lifeTimeProlonged = new List<ExportLifetimeContext<T>>();
            while (MefParts.TryTake(out item))
            {
                if (item != null && condition(item.Value))
                    item.Dispose();
                else 
                    lifeTimeProlonged.Add(item);
            }
            if (lifeTimeProlonged.Any())
            {
                //Add back again the parts not matching condition to the Concurrent bag
                foreach (var part in lifeTimeProlonged)
                {
                    MefParts.Add(part);
                }
            }
        }

        public void OnImportsSatisfied()
        {
            //marker interface
        }
   
        /// <summary>
        /// Resolves the mef part
        /// </summary>
        /// <returns></returns>
        public static T Resolve()
        {
            var factoryInstantiator = ServiceLocator.Current.GetInstance<ExportFactoryInstantiator<T>>();
            MefParts.Add(factoryInstantiator.Lifetime);
            return factoryInstantiator.Instance;
        }

    }
}



using System.ComponentModel.Composition;

namespace SomeAcme.Client.Infrastructure.IoC
{

    [Export]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class ExportFactoryInstantiator<T> : IPartImportsSatisfiedNotification
    {

        [Import]
        public ExportFactory<T> Factory { get; set; }

        public T Instance { get; private set; }

        private ExportLifetimeContext<T> _lifeTime;

        public ExportLifetimeContext<T> Lifetime
        {
            get { return _lifeTime; }
        } 

        public void OnImportsSatisfied()
        {
            _lifeTime = Factory.CreateExport();
            Instance = _lifeTime.Value;
        }

        public bool DisposeOnDemand()
        {
            if (_lifeTime == null)
                return false;
            _lifeTime.Dispose();
            return Instance == null;
        }

    }

}

To instantiate an object, you do:

 var somepart = MefFactory.Resolve();

When you are done using the object you can dispose it with:

 MefFactory.DisposeMefParts(); 

Please note, you can use a Predicate here to filter out which object you want to keep and which ones to dispose.

And once more, the immediate disposal of the object is not guaranteed, since GC will still control the true lifetime of objects. You can use GC.Collect(); to force releasing disposed objects, but that will usually degrade application performance.

But the techniques shown here will over time really improve your application by gaining control on the memory footprint your application uses.

Resources

[1] Enterprise Library: https://msdn.microsoft.com/library/cc467894.aspx
[2] ServiceLocator class: https://msdn.microsoft.com/en-us/library/microsoft.practices.servicelocation.servicelocator(v=pandp.51).aspx
[3] ServiceLocator pattern: https://msdn.microsoft.com/en-us/library/ff648968.aspx
[4] Managed Extensibility Framework: https://msdn.microsoft.com/en-us/library/dd460648(v=vs.110).aspx

2 comments:

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. I am a bit unsure on how to integratie this with prism?
    We would like to clean up references to old views on the Region.Views.CollectionChanged event.

    Could you give some tips on hooking this factory into prism?

    Or would the approach you described in http://toreaurstad.blogspot.dk/2012/09/freeing-up-memory-used-by-mef.html
    work better?

    Regards, Niels Harremoës

    ReplyDelete