.NET

Automating object pooling using IDisposable and finalizers

Last week we looked into the concept of object pooling, and how it can be used to increase performance by reusing objects that are expensive to create.

We also implemented a generic static class to make using object pools as simple as possible. Today I want to expand on the topic by showing how we can go even further and completely automate the pooling.

As a quick reminder, here is the public interface of the class we designed.

static class StaticObjectPool
{
    public static void Push<T>(T obj);
    public static bool TryPop<T>(out T obj);
    public static T PopOrDefault<T>();
    public static T PopOrNew<T>()
        where T : new();
}

For the full code, make sure to check last week’s post.

Making objects pool themselves

Let us say we have a Swimmer class that we want to keep in our object pool. The first thing we can do to automate this process is to give it a method that pushes it back into the pool when we are done with it. Let us call this method Dispose().

class Swimmer
{
    public void Dispose()
    {
        StaticObjectPool.Push(this);
    }
}

And while we are at it, why not also let the Swimmer handle it’s own creation, instead of relying on the user to know how to use the object pool. After all, the last thing we want is someone using our code to create more and more swimmers that all end up in the pool without ever reusing them.

We can do this easily by making the default constructor of our class private and instead using a static method to create new instances. Since we have full control over that method, we can make sure that our object pool is used correctly.

class Swimmer : IDisposable
{
    public static Swimmer GetOne()
    {
        return StaticObjectPool
            .PopOrDefault<Swimmer>()
            ?? new Swimmer();
    }
    private Swimmer()
    {
        // private constructor
        // to prevent improper usage
    }
    public void Dispose()
    {
        StaticObjectPool.Push(this);
    } 
}

And while we are at it, see how I quickly made our class implement the IDisposable interface. In itself this does very little. However, it means that we can use our class inside a using statement to automatically dispose it, if we need it only for a little while. Depending on what Swimmer does, this may or may not be a relevant feature.

using (var swimmer = Swimmer.GetOne())
{
    // Do something with the swimmer.
    // It will be automatically disposed
    // when we exit this block in any way,
    // including returns and exceptions.
}

Finalizers/Destructors

The above however also highlights a problem our class is having:

When not using the using statement – which means that we cannot keep our object around for long – we have to make sure to call the Dispose method manually, or our entire effort will be in vain.

If we do not dispose of our objects manually, they will simply be garbage collected after some time, and we have to create new ones when we need more.

We can solve this problem by using destructors(or finalizers). These are special methods – looking similar in to constructors (but are really nothing like them) – that are executed when an object is about to be garbage collected.

There are a lot of ifs and buts about the way finalizers work, and when they are called exactly – see this post by Eric Lippert for more information. However for our purposes it is suffice to assume that an object’s destructor will be called some time after we stop keeping references to the object.

Objects of classes with destructors are collected in two steps: First the destructor is executed. Only afterwards can the object be collected properly – assuming there are still no references to it at that time.

This last condition is the bases of the trick we will use: By adding our object to the pool in the destructor, we create a new reference to it which will keep it alive and ready to be used again in the future.

We can do this simply by calling the Dispose method in the destructor.

class Swimmer
{
    ~Swimmer()
    {
        this.Dispose();
    }
}

Note of caution:

I do not recommend blindly relying on destructors like this – especially in real time scenarios. Due to their somewhat unpredictable behaviour they can lead to a large number of created objects, even though much fewer would suffice, for as long as they are manually disposed.

Preventing invalid multi-pooling

At this point, we might notice another problem – in fact one that already existed in our original code above:

We could call the Dispose of our object more than once. This would add it to the pool multiple times, which means it can be taken out of it more than once as well. That is not at all the intended behaviour.

Luckily, we can avoid this problem fairly easily, by adding a boolean field that indicates whether the object is in the pool or not:

class Swimmer : IDisposable
{
    private bool disposed;
    public static Swimmer GetOne()
    {
        var swimmer = StaticObjectPool
            .PopOrDefault<Swimmer>()
            ?? new Swimmer();
        swimmer.disposed = true;
        return swimmer;
    }
    public void Dispose()
    {
        if(this.disposed)
            return;
        StaticObjectPool.Push(this);
        this.disposed = true;
    } 
}

This little change will make sure we can not accidentally add it to the pool more than once – at least if we are only talking about a single thread.

If we are dealing with multiple threads, some extra work is needed. In fact, there are a number of different ways in guaranteeing thread-safety:

We could for example use lock() on our object while adding it to the pool. This will surely work, however it is not a very nice solution, and can lead to unexpected result if any other code of ours is using a lock as well. (That being said, making our pooled objects available to more than one object at a time is problematic for a variety of reasons, but this is a topic for another post.)

Instead, we will make use of interlocked operations. In essence, what these allow us to do is both read and write a value in a completely thread-safe manner – and do so in a way that is extremely fast on modern hardware – without us having to bother about the details.

The existing methods are somewhat limited – for example there exist none for one-byte data types like booleans – but we can easily make due with what we have. We will chance our disposed field to be an integer, and use two constants to represent the two possible values we are interested in. Then, use Interlocked.Exchange to both set the field to the value representing true, while also returning its previous value, which will give us an indication on whether the object has already been added to the pool or not.

class Swimmer : IDisposable
{
    private const int disposedFalse = 0;
    private const int disposedTrue = 1;
    private int disposed = disposedFalse;
    public static Swimmer GetOne()
    {
        var swimmer = StaticObjectPool
            .PopOrDefault<Swimmer>()
            ?? new Swimmer();
        swimmer.disposed = disposedFalse;
        return swimmer;
    }
    public void Dispose()
    {
        if(Interlocked.Exchange(
            ref this.disposed, disposedTrue
            ) == disposedTrue)
            return;
        StaticObjectPool.Push(this);
    } 
}

With these changes, we have a completely thread-safe way of making sure our objects are never added to the pool more than once.

Note that we already made the retrieval from the pool entirely thread-safe last week, so we do not have to worry about it here.

Conclusion

This is where I will leave the post for now.

I hope it has been interesting to you, and that you maybe have learned something new about IDisposable, destructors/finalizers or Interlocked Operations. I encourage you to check out the various linked articles if you are interested in more on these topics.

Something that is still less than optimal is that with the solution presented here we would end up with a lot of code duplication. In essence we would have to add virtually the exact same code to every class we want to pool – somewhat defeating the purpose of automating this functionality.

Next week I will continue on that topic and see what we can do to automate even further.

Hint: Generics – and another neat trick of theirs – are sure to make a return in that post, so stay tuned!

Enjoy the pixels!

Paul Scharf

Paul is a self-publishing game developer. He believes that C# will play an ever growing role in the future of his industry. Next to working on a variety of projects he writes weekly technical blog posts on C#, graphics, and game development in general.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button