EntityFramework Core上下文实例池原理

【导读】无论是在我个人博客还是著作中,对于上下文实例池都只是通过大量文字描述来讲解其基本原理,而且也是浅尝辄止,导致我们对其认识仍是一知半解,本文我们摆源码,从源头开始分析

希望通过本文从源码的分析,我们大家都能了解到上注入下文和上下文实例池的区别在哪里,什么时候用上下文,什么时候用上下文实例池

友情提醒:此文略长,若心情烦躁、郁闷,无法静心,可以直接滑至文末总结或另安排时间再详细阅读本文

上下文实例池原理准备工作

上下文实例池和线程池原理从概念来上讲一样,都是可重用,但在原理实现上却有本质区别。EF Core定义上下文实例池接口即IDbContextPool,将其接口实现抽象为:租赁(Rent)和归还(Return)。如下:

public interface IDbContextPool
{DbContext Rent();bool Return([NotNull] DbContext context);
}

那么租赁和归还的机制是什么呢?接下来我们从注入上下文实例池开始讲解。

当我们在Startup中注入上下文和上下文实例池时,其他参数配置我们暂且忽略,从使用上二者最大区别在于,上下文可自定义设置生命周期,默认为Scope,而上下文实例池可自定义最大池大小,默认为128。

那么问题来了,上下文实例池所管理的上下文的生命周期到底是什么呢?我们一探源码究竟,参数细节判断部分这里忽略分析

private static void CheckContextConstructors<TContext>()where TContext : DbContext
{var declaredConstructors = typeof(TContext).GetTypeInfo().DeclaredConstructors.ToList();if (declaredConstructors.Count == 1&& declaredConstructors[0].GetParameters().Length == 0){throw new ArgumentException(CoreStrings.DbContextMissingConstructor(typeof(TContext).ShortDisplayName()));}
}

首先判断上下文必须有构造函数,因存在隐式默认无参构造函数,所以继续增强判断,构造函数参数不能为0,否则抛出异常

AddCoreServices<TContextImplementation>(serviceCollection,(sp, ob) =>{optionsAction(sp, ob);var extension = (ob.Options.FindExtension<CoreOptionsExtension>() ?? new CoreOptionsExtension()).WithMaxPoolSize(poolSize);((IDbContextOptionsBuilderInfrastructure)ob).AddOrUpdateExtension(extension);},ServiceLifetime.Singleton );

其次,以单例形式注入DbContextOptions,因每个上下文无论实例化多少次,其DbContextOptions不会发生改变

serviceCollection.TryAddSingleton(sp => new DbContextPool<TContextImplementation>(sp.GetService<DbContextOptions<TContextImplementation>>()));

然后,以单例形式注入上下文实例池接口实现,因为该实例中存在队列机制来维护上下文,所有此类必然为单例,同时,该实例需要用到DbContextOptions,所以提前注入DbContextOptions

serviceCollection.AddScoped<DbContextPool<TContextImplementation>.Lease>();

紧接着,以生命周期为Scope注入Lease类,此类作为上下文实例池嵌套密封类存在,从单词理解就是对上下文进行释放(归还)处理(接下来会讲到)

serviceCollection.AddScoped(sp => (TContextService)sp.GetService<DbContextPool<TContextImplementation>.Lease>().Context);

最后,这里就是上下文实例池所管理的上下文,其生命周期为Scope,不可更改

上下文实例池原理构造实现

首先给出上下文实例池中重要属性,以免越往下看一脸懵

private const int DefaultPoolSize = 32;private readonly ConcurrentQueue<TContext> _pool = new ConcurrentQueue<TContext>();private readonly Func<TContext> _activator;private int _maxSize;private int _count;private DbContextPoolConfigurationSnapshot _configurationSnapshot;

上述是对于注入上下文实例池所做的准备工作,接下来我们则来到上下文实例池具体实现

public DbContextPool([NotNull] DbContextOptions options)
{_maxSize = options.FindExtension<CoreOptionsExtension>()?.MaxPoolSize ?? DefaultPoolSize;options.Freeze();_activator = CreateActivator(options);if (_activator == null){throw new InvalidOperationException(CoreStrings.PoolingContextCtorError(typeof(TContext).ShortDisplayName()));}
}

在其构造中,获取自定义实例池最大大小,若未设置则以DefaultPoolSize为准,DefaultPoolSize定义为常量32

然后,防止实例化上下文后DbContextOptions配置发生更改,此时调用Freeze方法进行冻结

接下来则是实例化上下文,此时将其包裹在委托中,还未真正实例化,继续看上述CreateActivator方法实现

private static Func<TContext> CreateActivator(DbContextOptions 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>))){returnExpression.Lambda<Func<TContext>>(Expression.New(constructors[0], Expression.Constant(options))).Compile();}}return null;
}

简言之,上下文构造函数和参数有且只能有一个,而且参数必须类型必须是DbContextOptions,最后通过lambda表达式构造上下文委托

通过上述分析,正常情况下,我们知道设计如此,上下文只能是显式有参构造,而且参数必须只能有一个且必须是DbContextOptions。

但有些情况下,我们在上下文构造中确实需要使用注入实例,岂不玩不了,若存在这种需求,这里请参考之前文章(EntityFramework Core 3.x上下文构造函数可以注入实例呢?)

上下文实例池原理本质实现

上下文实例池构造得到最大实例池大小以及构造上下文委托(并未真正使用),接下来则是对上下文进行租赁(Rent)和归还(Return)处理

public virtual TContext Rent()
{if (_pool.TryDequeue(out var context)){Interlocked.Decrement(ref _count);((IDbContextPoolable)context).Resurrect(_configurationSnapshot);return context;}context = _activator();((IDbContextPoolable)context).SetPool(this);return context;
}

从上下文实例池中的队列去获取上下文,很显然,首次没有,于是就激活上下文委托,实例化上下文

若存在则将_count减1,然后将上下文的状态进行激活或复活处理

_count属性用来与获取到的实例池大小maxSize进行比较(至于如何比较,接下来归还用讲到),然后为防并发线程中断等机制,不能用简单的_count--,必须保持其原子性,所以用Interlocked,不清楚这个用法,补补基础

public virtual bool Return([NotNull] TContext context)
{if (Interlocked.Increment(ref _count) <= _maxSize){((IDbContextPoolable)context).ResetState();_pool.Enqueue(context);return true;}Interlocked.Decrement(ref _count);return false;
}

当上下文释放时(释放时做什么处理,下面会讲),首先将上下文状态重置,无非就是将上下文所跟踪的模型(变更追踪机制)进行关闭处理等等,这里就不做深入探讨,接下来则是将上下文归还上下文到队列中。

我们结合租赁和归还整体分析:设置池大小为32,若此时有33个请求,且处理时间较长,此时将直接租赁33个上下文,紧接着33个上下文陆续被释放,此时开始将0-31归还入队列,当索引为32时,此时_count为33,无法入队,怎么搞?此时将来到注入的Lease类释放处理

public TContext Context { get; private set; }void IDisposable.Dispose()
{if (_contextPool != null){if (!_contextPool.Return(Context)){((IDbContextPoolable)Context).SetPool(null);Context.Dispose();}_contextPool = null;Context = null;}
}

若请求超出自定义池大小,且请求处理周期很长,那么在释放时,余下上下文则不能再归还如队列,将直接释放,同时上下文实例池将结束掉自身不再具备对该上下文的维护处理能力

我们再次回到租赁方法,当队列中存在可用的上下文时,可以知道每次都重新实例化一个上下文和上下文实例池管理上下文的本质区别在于对Resurrect方法的处理。

 ((IDbContextPoolable)context).Resurrect(_configurationSnapshot);

我们再来看看该方法具体处理情况怎样,是否存在什么魔法从而有所影响性能的地方,我们在指定场景必须使用实例池呢?

void IDbContextPoolable.Resurrect(DbContextPoolConfigurationSnapshot configurationSnapshot)
{if (configurationSnapshot.AutoDetectChangesEnabled != null){ChangeTracker.AutoDetectChangesEnabled = configurationSnapshot.AutoDetectChangesEnabled.Value;ChangeTracker.QueryTrackingBehavior = configurationSnapshot.QueryTrackingBehavior.Value;ChangeTracker.LazyLoadingEnabled = configurationSnapshot.LazyLoadingEnabled.Value;ChangeTracker.CascadeDeleteTiming = configurationSnapshot.CascadeDeleteTiming.Value;ChangeTracker.DeleteOrphansTiming = configurationSnapshot.DeleteOrphansTiming.Value;}else{((IResettableService)_changeTracker)?.ResetState();}if (_database != null){_database.AutoTransactionsEnabled= configurationSnapshot.AutoTransactionsEnabled == null|| configurationSnapshot.AutoTransactionsEnabled.Value;}
}

哇,我们惊呆了,完全没啥,都不用我们再解释,只是简单设置变更追踪各个状态属性而已

毫无疑问,上下文实例确实可以重用上下文实例,若存在复杂的业务逻辑和吞吐量比较大的情况,使用上下文实例池很显然性能优于上下文,除此之外,二者在使用本质上并不存在太大性能差异

因为基于我们上述分析,若直接使用上下文,每次构建上下文实例,并不需要花费什么时间,同时,上下文实例池重用上下文后,也仅仅只是激活变更追踪属性,也不需要耗费什么时间。

这里我们也可以看到,上下文实例池和线程池区别很大,线程池重用线程,但创建线程开销可想而知,同时对于线程重用的机制也完全不一样,据我所知,线程池具有多个队列,对于线程池中的N个线程,有N+1个队列,每个线程都有一个本地队列和全局队列,至于选择哪个线程任务进入哪个队列看对应规则

分析至此,我们再对注入上下文和上下文实例池做一个完整的对比分析

???? 上下文周期默认为Scope且可自定义,而上下文实例池所管理的上下文周期为Scope,无法再更改

???? 上下文实例池默认大小为128,我们也可以重写其对应方法,若不给定maxSize(可空),则默认池大小为32

???? 若上下文实例池队列存在可租赁上下文,则取出,然后仅仅只是激活变更追踪响应属性,否则直接创建上下文实例

???? 若归还上下文超出上下文实例池队列大小(自定义池大小),则直接释放余下上下文,当然也就不再受上下文实例池所管理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307002.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

部署Dotnet Core应用到Kubernetes(一)

最近闲了点&#xff0c;写个大活&#xff1a;部署Dotnet应用到K8s。写在前边的话一直想完成这个主题。但这个主题实在太大了&#xff0c;各种拖延症的小宇宙不时爆发一下&#xff0c;结果就拖到了现在。这个主题&#xff0c;会是一个系列。在这个系列中&#xff0c;我会讨论将应…

java 配置参数_给你的JAVA程序配置参数(Properties的使用)

我们在写JAVA程序时&#xff0c;很多时候运行程序的参数是需要动态改变的测试时一系列参数&#xff0c;运行时一系列参数又或者数据库地址也需要配一套参数&#xff0c;以方便今后的动态部署这些变量的初始化&#xff0c;我们在写小DEMO时完全可以写死在JAVA文件中但程序需要发…

JAVA实验报告九异常处理_Java课后练习9(异常处理)

动手动脑1:import javax.swing.*;class AboutException {public static void main(String[] a){int i1, j0, k;ki/j;try{k i/j; // Causes division-by-zero exception//throw new Exception("Hello.Exception!");}catch ( ArithmeticException e){System.out.print…

asp.net core web api之异常

官方建议用app.UseExceptionHandler("/error")来集中处理异常&#xff0c;本例是一个具体的应用。比如项目中有一个ViewModel&#xff0c;要求Name最大长度为5/// <summary>/// 用户模型/// </summary>public class UserModel{/// <summary>/// ID…

java 实现 指派_TAP任务指派问题的汇编实现

近六周的课程设计&#xff0c;编了一个四百行的汇编程序&#xff0c;编的过程很不顺利&#xff0c;遇到种种意想不到的困难&#xff0c;但最终能够实现&#xff0c;可谓欣喜若狂&#xff0c;这期间学到了好多好多&#xff0c;遇到问题怎么精下心来解决&#xff0c;同时对汇编的…

.NET 5.0正式发布,有什么功能特性(翻译)

我们很高兴今天.NET5.0正式发布。这是一个重要的版本—其中也包括了C# 9和F# 5大量新特性和优秀的改进。微软和其他公司的团队已经在生产和性能测试环境中开始使用了。这些团队向我们反馈的结果比较令人满意&#xff0c;它证明了对性能提升及降低Web应用托管成本的机会有积极的…

Java 重写 多态性_java多态性重写overriding和重载overloading的区别

一、概述就Java而言&#xff0c;当有人问&#xff1a;什么是多态&#xff1f;将重载或重写的区别解释为一个可以接受的答案&#xff1f;如果您有一个抽象基类&#xff0c;它定义了一个没有实现的方法&#xff0c;并且您在子类中定义了该方法&#xff0c;那该方法是否仍然覆盖&a…

简单聊聊C#中lock关键字

为了避免多个线程同时操作同一资源&#xff0c;引起数据错误&#xff0c;通常我们会将这个资源加上锁&#xff0c;这样在同一时间只能有一个线程操作资源。在C#中我们使用lock关键字来锁定资源&#xff0c;那lock关键字是如何实现锁定的呢&#xff1f;我们先看一段代码&#xf…

idea如何导入java工程_Eclipse java web项目 ,导入IntelliJ IDEA 完整操作!

或许你用惯了Eclipse&#xff0c;有点排斥其他工具了&#xff0c;你写框架的时候&#xff0c;编译速度是不是特别慢啊&#xff1f;有时候还超过45秒&#xff0c;自动取消运行&#xff01;有时候代码是正常的&#xff0c;却无端端报错&#xff1f;下午吃个饭回来又好了&#xff…

行业思考 | 互联网对传统行业的降维打击

【行业思考】| 作者 / Edison Zhou这是EdisonTalk的第301篇原创内容在周一发布的推文《我在传统行业做数字化转型之预告篇》中&#xff0c;我提到互联网的发展和和竞争对传统行业起到了降维打击的作用&#xff0c;于是就有童鞋私下问我&#xff0c;为何这么说。今天就跟你聊聊这…

java aio复制文件_java复制文件的4种方式及拷贝文件到另一个目录下的实例代码...

尽管Java提供了一个可以处理文件的IO操作类。 但是没有一个复制文件的方法。 复制文件是一个重要的操作,当你的程序必须处理很多文件相关的时候。 然而有几种方法可以进行Java文件复制操作,下面列举出4中最受欢迎的方式。1. 使用FileStreams复制这是最经典的方式将一个文件的内…

BCVP开发者说第一期:Destiny.Core.Flow

沉静岁月&#xff0c;淡忘流年1项目简介Destiny.Core.FlowDestiny.Core.Flow是基于.NetCore平台&#xff0c;轻量级的模块化开发框架&#xff0c;Admin管理应用框架&#xff0c;旨在提升团队的快速开发输出能力&#xff0c;由常用公共操作类&#xff08;工具类、帮助类&#xf…

.NET Core 取消令牌:CancellationToken

在 .NET 开发中&#xff0c;CancellationToken&#xff08;取消令牌&#xff09;是一项比较重要的功能&#xff0c;掌握并合理的使用 CancellationToken 可以提升服务的性能。特别在异步编程中&#xff0c;我常常会以创建 Task 的方式利用多线程执行一些耗时或非核心业务逻辑&a…

java char short区别_java 彻底理解 byte char short int float long double

遇到过很多关于 数值类型范围的问题了&#xff0c;在这做一个总结&#xff0c;我们可以从多方面理解不同数值类型的所能表示的数值范围在这里我们只谈论 java中的数值类型首先说byte&#xff1a;这段是摘自jdk中 Byte.java中的源代码从这里可以看出 byte的取值范围&#xff1a;…

程序员过关斩将--从未停止过的系统架构设计步伐

“首先&#xff0c;这篇文章肯定会得罪一些人“其次&#xff0c;此文只代表我个人的意见&#xff0c;仅供参考从分层说起谈到系统架构的分层和系统领域边界的划分&#xff0c;每个架构师&#xff0c;每个技术经理&#xff0c;甚至每个程序员都有自己的一套想法。无论是怎么样的…

java流上传文档把磁盘撑满_BOOT目录磁盘占用满处理

背景&#xff1a;Ubuntu:16.04查看已安装启动镜像dpkg --get-selections |grep linux-image这里会列出目前已经安装的启动镜像&#xff0c;一般分两种&#xff0c;一种状态为“install”&#xff0c;一种为“deinstall”对于"deinstall"的镜像&#xff0c;一般为镜像…

C# 9.0 正式发布了(C# 9.0 on the record)

翻译自 Mads Torgersen 2020年11月10日的博文《C# 9.0 on the record》 [1]&#xff0c;Mads Torgersen 是微软 C# 语言的首席设计师&#xff0c;也是微软 .NET 团队的项目群经理。C# 9.0 正式发布正式宣布&#xff1a;C# 9.0 发布了&#xff01;早在5月&#xff0c;我就写了一…

磁带最优存储问题java实现_磁带的最优存储问题(贪心选择)

算法设计n个程序的平均读取时间&#xff1a;(贪心策略)在该题目中&#xff0c;要考虑综合因素&#xff1a;长度和读取概率。要求n个程序的平均读取时间最短。按照贪心策略&#xff0c;则每个程序的读取时间都应该最短。故&#xff1a;(1)计算每个程序的长度和读取概率的乘积。(…

BCVP第2期:项目已完成升级.NET5.0

(是时候拿出来这种图了)1开心的锣鼓想必这两天最热闹的几个词语&#xff0c;就是c#9.0、.net5.0还有conf大会了吧&#xff0c;当然还有大一统。其实&#xff0c;早在2019年年中&#xff0c;就已经引入了.NET5.0了&#xff0c;然后从2020-03-16开始&#xff0c;就一直在说.NET5.…

java 1..0 openjdk_java-1.7.0-openjdk-i386和java-7-openjdk-i386有什么区别

两个OpenJDK是一样的.但是OpenJDK与Oracle JDK略有不同.阅读this post了解更多信息.您为OpenJDK获取两个选项的原因是PROBABLY,您有两个安装的OpenJDK副本(或两个不同的引用到系统上的同一目录).为了进一步调查,请尝试使用ls -lh /usr/lib / jvm. /usr/lib / jvm通常是Java安装…