How is object pooling implemented or used in .NET Core?

Time:2022-8-5

foreword

The concept of pool is familiar to everyone. For example, we often hear database connection pool and thread pool. It is a performance optimization idea based on using a pre-allocated set of resources.

Simply put, an object pool is a container of objects designed to optimize the use of resources by pooling objects in a container and reusing these pooled objects as needed to meet performance requirements. When an object is activated, it is taken from the pool. When the object is deactivated, it is put back into the pool again, waiting for the next request. Object pools are generally used in scenarios where the initialization process of objects is expensive or frequently used.

How to implement or use object pooling in .NET?

The implementation of an object pool function has been built into the ASP.NET Core framework: Microsoft.Extensions.ObjectPool. If it is a console application, this extension library can be installed separately.

pooling strategy

First, to use ObjectPool, you need to create a pooling strategy that tells the object pool how you're going to create objects, and how to return them.

The policy is defined by implementing the interface IPooledObjectPolicy. The following is the simplest policy implementation:


public class FooPooledObjectPolicy : IPooledObjectPolicy<Foo>
{
    public Foo Create()
    {
        return new Foo();
    }

    public bool Return(Foo obj)
    {
        return true;
    }
}

If you have to define such a strategy every time you code, it will be troublesome. You can define a generic generic implementation yourself. A default generic implementation is also provided in Microsoft.Extensions.ObjectPool: DefaultPooledObjectPolicy<T> . If you don't need to define complex construction logic, just use the default one. Let's see how to use it.

Use of object pools

The principle used by the object pool is: there is borrowing and repayment, and it is not difficult to borrow again.

When there is no instance in the object pool, an instance is created and returned to the calling component; when there is an instance in the object pool, an existing instance is directly taken and returned to the calling component. And the process is thread-safe.

Microsoft.Extensions.ObjectPool provides a default object pool implementation: DefaultObjectPool<T> , which provides an interface for borrowing Get and returning operations. When creating an object pool, you need to provide the pooling policy IPooledObjectPolicy<T> as its construction parameter.


var policy = new DefaultPooledObjectPolicy<Foo>();
var pool = new DefaultObjectPool<Foo>(policy);

Let's look at a general example (C# 9.0 single-file full code):

using Microsoft.Extensions.ObjectPool;
using System;

var policy = new DefaultPooledObjectPolicy<Foo>();
var pool = new DefaultObjectPool<Foo>(policy);

// borrow
var item1 = pool.Get();
// return
pool.Return(item1);
Console.WriteLine("item 1: {0}", item1.Id);

// borrow
var item2 = pool.Get();
// return
pool.Return(item2);
Console.WriteLine("item 2: {0}", item2.Id);

Console.ReadKey();

public class Foo
{
    public string Id { get; set; } = Guid.NewGuid().ToString("N");
}

print result:

Knowing from the printed Id, item1 and item2 are the same object.

Let's take a look at what it would be like to borrow only:

// ...

// borrow
var item1 = pool.Get();
Console.WriteLine("item 1: {0}", item1.Id);

// borrow again
var item2 = pool.Get();
Console.WriteLine("item 2: {0}", item2.Id);

// ...

print result:

As you can see, the two objects are different instances. Therefore, when the calling component borrows an object instance from the object pool, it should be returned to the object pool immediately after use, so as to be reused and avoid excessive resource consumption due to the construction of new objects.

Specifies the object pool capacity

While creating DefaultObjectPool<T> , you can also specify a second parameter: the capacity of the object pool. It represents the maximum number of objects that can be taken out of the object pool. Objects taken beyond the specified number will not be pooled. Let me demonstrate, everyone will know what it means, please see the example:

using Microsoft.Extensions.ObjectPool;
using System;

var policy = new DefaultPooledObjectPolicy<Foo>();

// Specify a capacity of 2.
var pool = new DefaultObjectPool<Foo>(policy, 2);

// borrow 3
var item1 = pool.Get();
Console.WriteLine("item 1: {0}", item1.Id);
var item2 = pool.Get();
Console.WriteLine("item 2: {0}", item2.Id);
var item3 = pool.Get();
Console.WriteLine("item 3: {0}", item3.Id);

// 3 more
pool.Return(item1);
pool.Return(item2);
pool.Return(item3);


// borrow 3 more
var item4 = pool.Get();
Console.WriteLine("item 4: {0}", item4.Id);
var item5 = pool.Get();
Console.WriteLine("item 5: {0}", item5.Id);
var item6 = pool.Get();
Console.WriteLine("item 6: {0}", item6.Id);

Console.ReadKey();

Note that in the sample code, I specified a capacity of 2 for the object pool, then borrowed 3 and returned 3, and then borrowed 3 more. Let's take a look at the print result:

We see that item1 and item4 are the same object, and item2 and item5 are the same object. item3 and item6 are not the same object.

That is to say, when an object is taken out of the pool and exceeds the specified capacity, although the same number of objects are returned, the object pool is only allowed to accommodate 2 objects, and the third object will not be pooled.

Use in ASP.NET Core

The ASP.NET Core framework has Microsoft.Extensions.ObjectPool built-in and does not need to be installed separately. The official documentation has a usage example based on ASP.NET Core:

https://docs.microsoft.com/en-us/aspnet/core/performance/objectpool

This example pools StringBuilder. I will directly paste the official example here. In order to be more intuitive, I have simplified the irrelevant code.

First define a middleware:

public class BirthdayMiddleware
{
    private readonly RequestDelegate _next;

    public BirthdayMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, ObjectPool<StringBuilder> builderPool)
    {
        var stringBuilder = builderPool.Get();
        try
        {
            stringBuilder.Append("Hi");
            // other processing
            await context.Response.WriteAsync(stringBuilder.ToString());
        }
        finally // Guaranteed to return the object even if there is an error
        {
            builderPool.Return(stringBuilder);
        }
    }
}

Register the corresponding services and middleware in Startup:


public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

        services.TryAddSingleton<ObjectPool<StringBuilder>>(serviceProvider =>
        {
            var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
            var policy = new StringBuilderPooledObjectPolicy();
            return provider.Create(policy);
        });
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseMiddleware<BirthdayMiddleware>();
    }
}

This example uses DefaultObjectPoolProvider, which is the default object pool provider, so you can also customize your own object pool provider.

Summarize

The object pool function provided by Microsoft.Extensions.ObjectPool is quite flexible. Common scenarios can use the default pooling strategy, the default object pool and the default object pool provider to meet the requirements, or you can customize any of these components to achieve more special or complex requirements.

The principle of using the object pool is: there is borrowing and repayment, and it is not difficult to borrow again. When the calling component borrows an object instance from the object pool, it should be returned to the object pool immediately after use, so as to be reused and avoid affecting system performance due to excessive object initialization.

So far, this article about how to implement or use object pooling in .NET Core is introduced here. For more information about using object pooling in .NET Core, please search for previous articles by developpaer or continue to browse the related articles below. I hope you will enjoy more in the future. Support developpaer!

Recommended Today

The use of PHP command line extension Readline related functions

Table of contents Installation of the Readline extension Basic function operation read a line Command History List Related Operations Check Readline Status command prompt effect Examples of character callback operations Summarize The readline extension function implements an interface to access the GNU Readline library. These functions provide editable command lines. An example is in Bash […]