共用方式為


ASP.NET Core 中的 ObjectPool 重複使用物件

作者:Günther FoidlSteve GordonSamson Amaugo

注意

這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前的版本,請參閱 本文的 .NET 9 版本。

Microsoft.Extensions.ObjectPool 是 ASP.NET Core 基礎結構的一部分,可支援將一組物件保存在記憶體中以供重複使用,而不是讓物件作為垃圾進行回收。 所有 Microsoft.Extensions.ObjectPool 靜態和執行個體方法都是安全執行緒。

如果受控物件如下,應用程式可能會想要使用物件集區:

  • 配置/初始化的成本高昂。
  • 代表有限的資源。
  • 可預測且經常使用。

例如,ASP.NET Core 架構會在某些地方使用物件集區來重複使用 StringBuilder 執行個體。 StringBuilder 會配置和管理自己的緩衝區,以保存字元資料。 ASP.NET Core 會定期使用 StringBuilder 來實作功能,並重複使用這些功能以提供效能優勢。

物件共用不見得總是能改善效能:

  • 除非物件的初始化成本很高,否則從集區取得物件通常會比較慢。
  • 在取消配置集區之前,不會取消配置集區所管理的物件。

只有在收集了應用程式或程式庫的實際案例效能資料後,才使用物件共用。

注意:ObjectPool 不會針對所配置的物件數目設定限制,而是限制保留項目的物件數目。

ObjectPool 概念

當使用 DefaultObjectPoolProvider 時,T 會實作 IDisposable

  • 傳回至集區的項目將會受到處置。
  • 當 DI 處置集區時,集區中的所有項目也會一同處置。

注意:處置集區之後:

  • 呼叫 Get 會擲回 ObjectDisposedException
  • 呼叫 Return 會處置指定的項目。

重要 ObjectPool 類型和介面:

  • ObjectPool<T> :基本物件集區抽象概念。 用來取得和傳回物件。
  • PooledObjectPolicy<T>:實作此項目來自訂物件的建立方式,以及如何在傳回集區時重設它。 這可以傳遞到直接建構的物件集區中。
  • IResettable :當傳回物件集區時,自動重設物件。

ObjectPool 可以透過多種方式在應用程式中使用:

  • 具現化集區。
  • 相依性插入 (DI) 中的集區註冊為執行個體。
  • 在 DI 中註冊 ObjectPoolProvider<>,並將其作爲中心使用。

如何使用 ObjectPool

呼叫 Get 以取得物件,呼叫 Return 以傳回物件。 不需要傳回每個物件。 如果未傳回物件,其即會被當作垃圾進行回收。

ObjectPool 範例

下列程式碼範例:

  • ObjectPoolProvider 新增至相依性插入 (DI) 容器。
  • 實作 IResettable 介面,以在傳回至物件集區時自動清除緩衝區的內容。
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.ObjectPool;
using System.Security.Cryptography;

var builder = WebApplication.CreateBuilder(args);

builder.Services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();

builder.Services.TryAddSingleton<ObjectPool<ReusableBuffer>>(serviceProvider =>
{
    var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
    var policy = new DefaultPooledObjectPolicy<ReusableBuffer>();
    return provider.Create(policy);
});

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

// return the SHA256 hash of a word 
// https://localhost:7214/hash/SamsonAmaugo
app.MapGet("/hash/{name}", (string name, ObjectPool<ReusableBuffer> bufferPool) =>
{

    var buffer = bufferPool.Get();
    try
    {
        // Set the buffer data to the ASCII values of a word
        for (var i = 0; i < name.Length; i++)
        {
            buffer.Data[i] = (byte)name[i];
        }

        Span<byte> hash = stackalloc byte[32];
        SHA256.HashData(buffer.Data.AsSpan(0, name.Length), hash);
        return "Hash: " + Convert.ToHexString(hash);
    }
    finally
    {
        // Data is automatically reset because this type implemented IResettable
        bufferPool.Return(buffer); 
    }
});
app.Run();

public class ReusableBuffer : IResettable
{
    public byte[] Data { get; } = new byte[1024 * 1024]; // 1 MB

    public bool TryReset()
    {
        Array.Clear(Data);
        return true;
    }
}

注意: 當集區式類型 T 未實作 IResettable 時,則可以使用自訂 PooledObjectPolicy<T> 來重設物件的狀態,再將其傳回集區。

Microsoft.Extensions.ObjectPool 是 ASP.NET Core 基礎結構的一部分,可支援將一組物件保存在記憶體中以供重複使用,而不是讓物件作為垃圾進行回收。 所有 Microsoft.Extensions.ObjectPool 靜態和執行個體方法都是安全執行緒。

如果受控物件如下,應用程式可能會想要使用物件集區:

  • 配置/初始化的成本高昂。
  • 代表有限的資源。
  • 可預測且經常使用。

例如,ASP.NET Core 架構會在某些地方使用物件集區來重複使用 StringBuilder 執行個體。 StringBuilder 會配置和管理自己的緩衝區,以保存字元資料。 ASP.NET Core 會定期使用 StringBuilder 來實作功能,並重複使用這些功能以提供效能優勢。

物件共用不見得總是能改善效能:

  • 除非物件的初始化成本很高,否則從集區取得物件通常會比較慢。
  • 在取消配置集區之前,不會取消配置集區所管理的物件。

只有在收集了應用程式或程式庫的實際案例效能資料後,才使用物件共用。

注意:ObjectPool 不會針對所配置的物件數目設定限制,而是限制保留項目的物件數目。

概念

當使用 DefaultObjectPoolProvider 時,T 會實作 IDisposable

  • 傳回至集區的項目將會受到處置。
  • 當 DI 處置集區時,集區中的所有項目也會一同處置。

注意:處置集區之後:

  • 呼叫 Get 會擲回 ObjectDisposedException
  • 呼叫 Return 會處置指定的項目。

重要 ObjectPool 類型和介面:

  • ObjectPool<T> :基本物件集區抽象概念。 用來取得和傳回物件。
  • PooledObjectPolicy<T> :實作此項目來自訂物件的建立方式,以及如何在傳回集區時重設物件。 這可以傳遞至直接建構的物件集區,或
  • Create :作為建立物件集區的中心。
  • IResettable :當傳回物件集區時,自動重設物件。

ObjectPool 可以透過多種方式在應用程式中使用:

  • 具現化集區。
  • 相依性插入 (DI) 中的集區註冊為執行個體。
  • 在 DI 中註冊 ObjectPoolProvider<>,並將其作爲中心使用。

如何使用 ObjectPool

呼叫 Get 以取得物件,呼叫 Return 以傳回物件。 您不需要傳回每個物件。 如果您未傳回物件,其即會被當作垃圾進行回收。

ObjectPool 範例

下列程式碼範例:

  • ObjectPoolProvider 新增至相依性插入 (DI) 容器。
  • ObjectPool<StringBuilder> 新增和設定至 DI 容器。
  • BirthdayMiddleware 加入。
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.ObjectPool;
using ObjectPoolSample;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

builder.Services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
builder.Services.TryAddSingleton<ObjectPool<StringBuilder>>(serviceProvider =>
{
    var provider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
    var policy = new Microsoft.Extensions.ObjectPool.StringBuilderPooledObjectPolicy();
    return provider.Create(policy);
});

builder.Services.AddWebEncoders();

var app = builder.Build();

// Test using /?firstname=Steve&lastName=Gordon&day=28&month=9
app.UseMiddleware<BirthdayMiddleware>();

app.MapGet("/", () => "Hello World!");

app.Run();

下列程式碼會實作 BirthdayMiddleware

using System.Text;
using System.Text.Encodings.Web;
using Microsoft.Extensions.ObjectPool;

namespace ObjectPoolSample;

public class BirthdayMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context, 
                                  ObjectPool<StringBuilder> builderPool)
    {
        if (context.Request.Query.TryGetValue("firstName", out var firstName) &&
            context.Request.Query.TryGetValue("lastName", out var lastName) && 
            context.Request.Query.TryGetValue("month", out var month) &&                 
            context.Request.Query.TryGetValue("day", out var day) &&
            int.TryParse(month, out var monthOfYear) &&
            int.TryParse(day, out var dayOfMonth))
        {                
            var now = DateTime.UtcNow; // Ignoring timezones.

            // Request a StringBuilder from the pool.
            var stringBuilder = builderPool.Get();

            try
            {
                stringBuilder.Append("Hi ")
                    .Append(firstName).Append(" ").Append(lastName).Append(". ");

                var encoder = context.RequestServices.GetRequiredService<HtmlEncoder>();

                if (now.Day == dayOfMonth && now.Month == monthOfYear)
                {
                    stringBuilder.Append("Happy birthday!!!");

                    var html = encoder.Encode(stringBuilder.ToString());
                    await context.Response.WriteAsync(html);
                }
                else
                {
                    var thisYearsBirthday = new DateTime(now.Year, monthOfYear, 
                                                                    dayOfMonth);

                    int daysUntilBirthday = thisYearsBirthday > now 
                        ? (thisYearsBirthday - now).Days 
                        : (thisYearsBirthday.AddYears(1) - now).Days;

                    stringBuilder.Append("There are ")
                        .Append(daysUntilBirthday).Append(" days until your birthday!");

                    var html = encoder.Encode(stringBuilder.ToString());
                    await context.Response.WriteAsync(html);
                }
            }
            finally // Ensure this runs even if the main code throws.
            {
                // Return the StringBuilder to the pool.
                builderPool.Return(stringBuilder); 
            }

            return;
        }

        await _next(context);
    }
}

Microsoft.Extensions.ObjectPool 是 ASP.NET Core 基礎結構的一部分,可支援將一組物件保存在記憶體中以供重複使用,而不是讓物件作為垃圾進行回收。

如果受控物件如下,則您可能會想要使用物件集區:

  • 配置/初始化的成本高昂。
  • 代表一些有限的資源。
  • 可預測且經常使用。

例如,ASP.NET Core 架構會在某些地方使用物件集區來重複使用 StringBuilder 執行個體。 StringBuilder 會配置和管理自己的緩衝區,以保存字元資料。 ASP.NET Core 會定期使用 StringBuilder 來實作功能,並重複使用這些功能以提供效能優勢。

物件共用不見得總是能改善效能:

  • 除非物件的初始化成本很高,否則從集區取得物件通常會比較慢。
  • 在取消配置集區之前,不會取消配置集區所管理的物件。

只有在收集了應用程式或程式庫的實際案例效能資料後,才使用物件共用。

警告:ObjectPool 不會實作 IDisposable。 不建議將其與需要處置的類型搭配使用。 ASP.NET Core 3.0 和更新版本中的 ObjectPool 支援 IDisposable

注意:ObjectPool 不會針對要配置的物件數目設定限制,而是會限制保留項目的物件數目。

概念

ObjectPool<T> - 基本物件集區抽象概念。 用來取得和傳回物件。

PooledObjectPolicy<T> - 實作此項目來自訂物件的建立方式,以及如何在傳回集區時重設物件。 這可以傳遞至您直接建構的物件集區...OR

Create 做為建立物件集區的中心。

ObjectPool 可以透過多種方式在應用程式中使用:

  • 具現化集區。
  • 相依性插入 (DI) 中的集區註冊為執行個體。
  • 在 DI 中註冊 ObjectPoolProvider<>,並將其作爲中心使用。

如何使用 ObjectPool

呼叫 Get 以取得物件,呼叫 Return 以傳回物件。 您不需要傳回每個物件。 如果您未傳回物件,其即會被當作垃圾進行回收。

ObjectPool 範例

下列程式碼範例:

  • ObjectPoolProvider 新增至相依性插入 (DI) 容器。
  • ObjectPool<StringBuilder> 新增和設定至 DI 容器。
  • BirthdayMiddleware 加入。
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);
        });

        services.AddWebEncoders();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        
        // Test using /?firstname=Steve&lastName=Gordon&day=28&month=9
        app.UseMiddleware<BirthdayMiddleware>(); 
    }
}

下列程式碼會實作 BirthdayMiddleware

public class BirthdayMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context, 
                                  ObjectPool<StringBuilder> builderPool)
    {
        if (context.Request.Query.TryGetValue("firstName", out var firstName) &&
            context.Request.Query.TryGetValue("lastName", out var lastName) && 
            context.Request.Query.TryGetValue("month", out var month) &&                 
            context.Request.Query.TryGetValue("day", out var day) &&
            int.TryParse(month, out var monthOfYear) &&
            int.TryParse(day, out var dayOfMonth))
        {                
            var now = DateTime.UtcNow; // Ignoring timezones.

            // Request a StringBuilder from the pool.
            var stringBuilder = builderPool.Get();

            try
            {
                stringBuilder.Append("Hi ")
                    .Append(firstName).Append(" ").Append(lastName).Append(". ");

                var encoder = context.RequestServices.GetRequiredService<HtmlEncoder>();

                if (now.Day == dayOfMonth && now.Month == monthOfYear)
                {
                    stringBuilder.Append("Happy birthday!!!");

                    var html = encoder.Encode(stringBuilder.ToString());
                    await context.Response.WriteAsync(html);
                }
                else
                {
                    var thisYearsBirthday = new DateTime(now.Year, monthOfYear, 
                                                                    dayOfMonth);

                    int daysUntilBirthday = thisYearsBirthday > now 
                        ? (thisYearsBirthday - now).Days 
                        : (thisYearsBirthday.AddYears(1) - now).Days;

                    stringBuilder.Append("There are ")
                        .Append(daysUntilBirthday).Append(" days until your birthday!");

                    var html = encoder.Encode(stringBuilder.ToString());
                    await context.Response.WriteAsync(html);
                }
            }
            finally // Ensure this runs even if the main code throws.
            {
                // Return the StringBuilder to the pool.
                builderPool.Return(stringBuilder); 
            }

            return;
        }

        await _next(context);
    }
}