Memory Usage, Autofac and Transient Objects

Halfwit 2 makes heavy use of dependency injection using the awesome Autofac IoC container. Almost every ViewModel is resolved as a dependency by Autofac. For example, check out the constructor on the home timeline's ViewModel class:

public HomeViewModel(
    Dispatcher dispatcher,
    IMessenger messenger, 
    MainViewModel host,
    Func<TimelineViewModel, ITweetable, StatusViewModel> statusFactory,
    Func<StatusViewModel, ConversationViewModel> conversationFactory,
    Func<TimelineViewModel, TweetViewModel> tweetUpdateFactory,
    Func<TimelineViewModel, IHalfwitUser, DirectMessageViewModel> messageUpdateFactory,
    IEnumerable<TwitterPoller> poller)

Everything that the home timeline needs is passed into the constructor, either explicitly or in the form of a Func so the timeline can generate multiple instances of an object on the fly.

One parameter in particular is noteworthy here:

Func<TimelineViewModel, ITweetable, StatusViewModel> statusFactory,

That's the factory function that the timeline uses to create tweets. Each time a new set of tweets come in, the home timeline will call that function and generate a new StatusViewModel instance for every tweet.

There's a problem with this code, though, and it's one I wasn't aware of until just a few days ago.

Autofac, by default, tracks the instances of every object it creates. That means that the container itself holds a reference to every instance, and those instances therefore cannot be garbage collected until the container itself is collected. That's true even for factory-generated instances (i.e. when you resolve a Func and use that to generate new objects).

This has a big impact when it comes to the StatusViewModel class we just talked about, since Halfwit only keeps 100 of them around at any time, and expects the older ones to "disappear". It turned out that they weren't disappearing when I removed them from the timeline's list of tweets, because the Autofac container was keeping a reference to them!

This meant that if you left Halfwit 2 running long enough, the memory usage would continually grow. I was seeing instances of halfwit2.exe using as much as 450MB of memory!

The solution is to tell Autofac that you want to "own" the instances and will be responsible for their disposal, and you do that by asking for a special Owned<T> type rather than T directly. Check out this article in the Autofac documentation: Owned Instances.

So our StatusViewModel factory dependency now looks like this:

Func<TimelineViewModel, ITweetable, Owned<StatusViewModel>> statusFactory,

See how we're now asking for a Func that returns Owned<StatusViewModel>? That's how we tell Autofac that we will be in control of each StatusViewModel instance's lifetime. When we remove the tweet from the list, the garbage collector now knows that it can be cleaned up.

The latest version of the Halfwit 2 beta now sits happily around 40MB of memory on my 2GB netbook no matter how long I leave it running for. The amount of memory it uses will vary from PC to PC, since the garbage collector will only bother collecting instances when it decides that there's enough "memory pressure" for it to do so, and that depends on how much RAM you have in your machine.

This is definitely a trap for new players in the world of IoC containers. Not every container does this level of tracking, but it's worth double checking the one you're using, and learning about how to tell the container that you want control over the dependencies' lifetimes.

Update As Nick says in the comments, Autofac actually only tracks objects that implement IDisposable. MVVM Light's ViewModelBase does, so that's why my tweets were being tracked. Still, I would recommend depending on Owned<T> for anything if you want control over its lifetime, regardless of whether it implements IDisposable.

.net wpf autofac halfwit
Posted by: Matt Hamilton
Last revised: 20 Feb, 2017 08:20 PM History

Trackbacks