EF Core 6中的DbContextPool

起因

前几天通过GitHub看到EF Core 修改DbContextPool默认大小,有原来的128改为1024.便打开DbContextPool源码学习了一番.
EF Core 6中将DbContextPool的默认大小从128改为1024

DbContextPool源码

public class DbContextPool<TContext> : IDbContextPool<TContext>, IDisposable, IAsyncDisposable
    where TContext : DbContext
{
    //默认大小由原来的128,改为1024 
    public const int DefaultPoolSize = 1024;

    //队列
    private readonly ConcurrentQueue<IDbContextPoolable> _pool = new();

    //缓存创建DbContext对象(或者集成DbContext对象的实例)委托
    //该委托 相当于new DbContext(),性能比反射高很多
    private readonly Func<DbContext> _activator;

    private int _maxSize;
    private int _count;

    //
    public DbContextPool(DbContextOptions<TContext> options)
    {
        _maxSize = options.FindExtension<CoreOptionsExtension>()?.MaxPoolSize ?? DefaultPoolSize;

        if (_maxSize <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(CoreOptionsExtension.MaxPoolSize), CoreStrings.InvalidPoolSize);
        }

        options.Freeze();

        _activator = CreateActivator(options);
    }

    //1.获取DbContext对象或继承DbContext对象构造方法和参数
    //2.通过表达式树创建并返回一个委托
    private static Func<DbContext> CreateActivator(DbContextOptions<TContext> options)
    {
        var constructors = typeof(TContext).GetTypeInfo().DeclaredConstructors.Where(c => !c.IsStatic && c.IsPublic).ToArray();

        if (constructors.Length == 1)
        {
            var parameters = constructors[0].GetParameters();
            if (parameters.Length == 1
                && (parameters[0].ParameterType == typeof(DbContextOptions)
                    || parameters[0].ParameterType == typeof(DbContextOptions<TContext>)))
            {
                return Expression.Lambda<Func<TContext>>(Expression.New(constructors[0], Expression.Constant(options))).Compile();
            }
        }

        throw new InvalidOperationException(CoreStrings.PoolingContextCtorError(typeof(TContext).ShortDisplayName()));
    }

    //1. 如果DbContext池有DbContext对象,从队列中获取
    //2. 如果队里中,没有DbContext,则由委托创建DbContext
    public virtual IDbContextPoolable Rent()
    {
        if (_pool.TryDequeue(out var context))
        {
            Interlocked.Decrement(ref _count);

            Check.DebugAssert(_count >= 0, $"_count is {_count}");

            return context;
        }

        context = _activator();

        context.SnapshotConfiguration();

        return context;
    }

    // 1.当DbContext对象使用完毕,队列中的DbContext数量没有超过默认值1024,则DbContext放入队列中
    // 2.如果队列中超过1024这个默认值,则DbContext进行释放
    public virtual void Return(IDbContextPoolable context)
    {
        if (Interlocked.Increment(ref _count) <= _maxSize)
        {
            context.ResetState();

            _pool.Enqueue(context);
        }
        else
        {
            PooledReturn(context);
        }
    }

    // 归还DbContext的异步实现
    public virtual async ValueTask ReturnAsync(IDbContextPoolable context, CancellationToken cancellationToken = default)
    {
        if (Interlocked.Increment(ref _count) <= _maxSize)
        {
            await context.ResetStateAsync(cancellationToken).ConfigureAwait(false);

            _pool.Enqueue(context);
        }
        else
        {
            PooledReturn(context);
        }
    }

    private void PooledReturn(IDbContextPoolable context)
    {
        Interlocked.Decrement(ref _count);

        Check.DebugAssert(_maxSize == 0 || _pool.Count <= _maxSize, $"_maxSize is {_maxSize}");

        context.ClearLease();
        context.Dispose();
    }

    public virtual void Dispose()
    {
        _maxSize = 0;

        while (_pool.TryDequeue(out var context))
        {
            context.ClearLease();
            context.Dispose();
        }
    }

    public virtual async ValueTask DisposeAsync()
    {
        _maxSize = 0;

        while (_pool.TryDequeue(out var context))
        {
            context.ClearLease();
            await context.DisposeAsync().ConfigureAwait(false);
        }
    }
}

还是学到了不少东西,比如使用表达式树创建委托对象,在缓存委托对象,而不是通过反射的方式创建(性能不佳).还有就是使用原子操作判断队列的数量,是否达到最大值,使用ConncurentQueue队列而不是使用Queue等.

秋风 2021-05-27