├── .gitattributes ├── .gitignore ├── Demo.cs ├── README.md ├── SafeObjectPool.csproj ├── SafeObjectPool.xml ├── SafeObjectPool ├── DefaultPolicy.cs ├── IObjectPool.cs ├── IPolicy.cs ├── Object.cs └── ObjectPool.cs └── key.snk /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | package-lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | 144 | # TODO: Un-comment the next line if you do not want to checkin 145 | # your web deploy settings because they may include unencrypted 146 | # passwords 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # NuGet Packages 151 | *.nupkg 152 | # The packages folder can be ignored because of Package Restore 153 | **/packages/* 154 | # except build/, which is used as an MSBuild target. 155 | !**/packages/build/ 156 | # Uncomment if necessary however generally it will be regenerated when needed 157 | #!**/packages/repositories.config 158 | # NuGet v3's project.json files produces more ignoreable files 159 | *.nuget.props 160 | *.nuget.targets 161 | 162 | # Microsoft Azure Build Output 163 | csx/ 164 | *.build.csdef 165 | 166 | # Microsoft Azure Emulator 167 | ecf/ 168 | rcf/ 169 | 170 | # Microsoft Azure ApplicationInsights config file 171 | ApplicationInsights.config 172 | 173 | # Windows Store app package directory 174 | AppPackages/ 175 | BundleArtifacts/ 176 | 177 | # Visual Studio cache files 178 | # files ending in .cache can be ignored 179 | *.[Cc]ache 180 | # but keep track of directories ending in .cache 181 | !*.[Cc]ache/ 182 | 183 | # Others 184 | ClientBin/ 185 | [Ss]tyle[Cc]op.* 186 | ~$* 187 | *~ 188 | *.dbmdl 189 | *.dbproj.schemaview 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ -------------------------------------------------------------------------------- /Demo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | class SafeObjectPoolDemo 8 | { 9 | 10 | public void test() 11 | { 12 | 13 | var pool = new SafeObjectPool.ObjectPool(10, () => new MemoryStream(), obj => 14 | { 15 | if (DateTime.Now.Subtract(obj.LastGetTime).TotalSeconds > 3) 16 | { 17 | var dt = Encoding.UTF8.GetBytes("我是中国人"); 18 | obj.Value.Write(dt, 0, dt.Length); 19 | } 20 | }); 21 | 22 | 23 | for (var a = 0; a < 100; a++) 24 | { 25 | new Thread(() => 26 | { 27 | 28 | for (var b = 0; b < 1000; b++) 29 | { 30 | var item = pool.Get(); 31 | pool.Return(item); 32 | } 33 | 34 | Console.WriteLine(pool.StatisticsFullily); 35 | 36 | 37 | }).Start(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 介绍 2 | 3 | 数据库操作通常是 new SqlConnection()、 Open()、 使用完后 Close(),其实 ado.net 驱动已经实现了连接池管理,不然每次创建、连接、释放相当浪费性能。假设网站的访问在某一时刻突然爆增100万次,new 100万个SqlConnection对象显然会炸掉服务,连接对象,每次创建,connect,disconnect,disponse,显然开销很大。目前看来,最适合做连接对象的池子,对象池里的连接对象,保持长链接,效率最大化。 4 | 5 | ado.net自带的链接池不完美,比如占满的时候再请求会报错。ObjectPool 解决池用尽后,再请求不报错,排队等待机制。 6 | 7 | 对象池容器化管理一批对象,重复使用从而提升性能,有序排队申请,使用完后归还资源。 8 | 9 | 对象池在超过10秒仍然未获取到对象时,报出异常: 10 | 11 | > SafeObjectPool 获取超时(10秒),设置 Policy.IsThrowGetTimeoutException 可以避免该异常。 12 | 13 | ## 与dapper比武测试 14 | 15 | ```csharp 16 | [HttpGet("vs_gen")] 17 | async public Task vs_gen() { 18 | var select = Tag.Select; 19 | var count = await select.CountAsync(); 20 | var items = await select.Page(page, limit).ToListAsync(); 21 | 22 | return new { count, items }; 23 | } 24 | 25 | [HttpGet("vs_dapper")] 26 | async public Task vs_dapper() { 27 | var conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=cms;Pooling=true;Max Pool Size=11"); 28 | conn.Open(); 29 | var count = await conn.ExecuteScalarAsync("SELECT count(1) FROM[dbo].[tag] a"); 30 | //conn.Close(); 31 | 32 | //conn = new SqlConnection("Data Source=.;Integrated Security=True;Initial Catalog=cms;Pooling=true;Max Pool Size=11"); 33 | //conn.Open(); 34 | var items = await conn.QueryAsync("SELECT TOP 20 a.[id], a.[parent_id], a.[name] FROM[dbo].[tag] a"); 35 | conn.Close(); 36 | 37 | return new { count, items }; 38 | } 39 | ``` 40 | 41 | 连接池最大为:10,11 42 | 43 | ab -c 10 -n 1000 -s 6000 测试结果差不多 44 | 45 | -c 100 时,vs_dapper直接挂了,vs_gen没影响(使用了SafeObjectPool) 46 | 47 | > 实践证明ado.net过于暴露,突然的高并发招架不住。 48 | 49 | ## 应用场景 50 | 51 | * 数据库连接对象池 52 | > [SQLServer连接池](https://github.com/2881099/dng.Mssql/blob/master/Mssql/SqlConnectionPool.cs)、[MySQL连接池](https://github.com/2881099/dng.Mysql/blob/master/MySql.Data.MySqlClient/MySqlConnectionPool.cs)、[PostgreSQL连接池](https://github.com/2881099/dng.Pgsql/blob/master/Npgsql/NpgsqlConnectionPool.cs)、[Redis连接池](https://github.com/2881099/csredis/blob/master/src/CSRedisCore/RedisClientPool.cs) 53 | * redis连接对象池 54 | 55 | ## 安装 56 | 57 | > Install-Package SafeObjectPool 58 | 59 | ## 使用方法 60 | 61 | ```csharp 62 | var pool = new SafeObjectPool.ObjectPool(10, () => new MemoryStream(), obj => { 63 | if (DateTime.Now.Subtract(obj.LastGetTime).TotalSeconds > 5) { 64 | // 对象超过5秒未活动,进行操作 65 | } 66 | }); 67 | 68 | var obj = pool.Get(); //借 69 | pool.Return(obj); //归还 70 | 71 | //或者 using 自动归还 72 | using (var obj = pool.Get()) { 73 | 74 | } 75 | ``` 76 | 77 | ## SQLServer连接池 78 | 79 | ```csharp 80 | var pool = new System.Data.SqlClient.SqlConnectionPool("名称", connectionString, 可用时触发的委托, 不可用时触发的委托); 81 | var conn = pool.Get(); 82 | 83 | try { 84 | // 使用 ... 85 | pool.Return(conn); //正常归还 86 | } catch (Exception ex) { 87 | pool.Return(conn, ex); //发生错误时归还 88 | } 89 | ``` 90 | 91 | ## MySQL连接池 92 | 93 | ```csharp 94 | var pool = new MySql.Data.MySqlClient.MySqlConnectionPool("名称", connectionString, 可用时触发的委托, 不可用时触发的委托); 95 | var conn = pool.Get(); 96 | 97 | try { 98 | // 使用 ... 99 | pool.Return(conn); //正常归还 100 | } catch (Exception ex) { 101 | pool.Return(conn, ex); //发生错误时归还 102 | } 103 | ``` 104 | 105 | ## PostgreSQL连接池 106 | 107 | ```csharp 108 | var pool = new Npgsql.NpgsqlConnectionPool("名称", connectionString, 可用时触发的委托, 不可用时触发的委托); 109 | var conn = pool.Get(); 110 | 111 | try { 112 | // 使用 ... 113 | pool.Return(conn); //正常归还 114 | } catch (Exception ex) { 115 | pool.Return(conn, ex); //发生错误时归还 116 | } 117 | ``` 118 | 119 | ## Redis连接池 120 | 121 | ```csharp 122 | var connectionString = "127.0.0.1[:6379],password=,defaultDatabase=13,poolsize=50,ssl=false,writeBuffer=10240,prefix=key前辍"; 123 | var pool = new CSRedis.RedisClientPool("名称", connectionString, client => { }); 124 | var conn = pool.Get(); 125 | 126 | try { 127 | // 使用 ... 128 | pool.Return(conn); //正常归还 129 | } catch (Exception ex) { 130 | pool.Return(conn, ex); //发生错误时归还 131 | } 132 | ``` 133 | 134 | # 更多连接池正在开发中。。。 -------------------------------------------------------------------------------- /SafeObjectPool.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net45;net40 5 | SafeObjectPool 6 | SafeObjectPool 7 | SafeObjectPool 8 | 3.0.0 9 | true 10 | https://github.com/2881099/SafeObjectPool 11 | 应用场景:连接池,资源池等等 12 | https://github.com/2881099/SafeObjectPool 13 | Pool Pooling PoolSize ObjectPool 连接池 14 | git 15 | MIT 16 | $(AssemblyName) 17 | $(AssemblyName) 18 | true 19 | true 20 | 3 21 | true 22 | key.snk 23 | 24 | 25 | 26 | SafeObjectPool.xml 27 | 28 | 29 | 30 | net40 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /SafeObjectPool.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SafeObjectPool 5 | 6 | 7 | 8 | 9 | 是否可用 10 | 11 | 12 | 13 | 14 | 不可用错误 15 | 16 | 17 | 18 | 19 | 不可用时间 20 | 21 | 22 | 23 | 24 | 将对象池设置为不可用,后续 Get/GetAsync 均会报错,同时启动后台定时检查服务恢复可用 25 | 26 | 27 | 28 | 由【可用】变成【不可用】时返回true,否则返回false 29 | 30 | 31 | 32 | 统计对象池中的对象 33 | 34 | 35 | 36 | 37 | 统计对象池中的对象(完整) 38 | 39 | 40 | 41 | 42 | 获取资源 43 | 44 | 超时 45 | 46 | 47 | 48 | 49 | 获取资源 50 | 51 | 52 | 53 | 54 | 55 | 使用完毕后,归还资源 56 | 57 | 对象 58 | 是否重新创建 59 | 60 | 61 | 62 | 名称 63 | 64 | 65 | 66 | 67 | 池容量 68 | 69 | 70 | 71 | 72 | 默认获取超时设置 73 | 74 | 75 | 76 | 77 | 空闲时间,获取时若超出,则重新创建 78 | 79 | 80 | 81 | 82 | 异步获取排队队列大小,小于等于0不生效 83 | 84 | 85 | 86 | 87 | 获取超时后,是否抛出异常 88 | 89 | 90 | 91 | 92 | 监听 AppDomain.CurrentDomain.ProcessExit/Console.CancelKeyPress 事件自动释放 93 | 94 | 95 | 96 | 97 | 后台定时检查可用性间隔秒数 98 | 99 | 100 | 101 | 102 | 对象池的对象被创建时 103 | 104 | 返回被创建的对象 105 | 106 | 107 | 108 | 销毁对象 109 | 110 | 资源对象 111 | 112 | 113 | 114 | 从对象池获取对象超时的时候触发,通过该方法统计 115 | 116 | 117 | 118 | 119 | 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 120 | 121 | 资源对象 122 | 123 | 124 | 125 | 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 126 | 127 | 资源对象 128 | 129 | 130 | 131 | 归还对象给对象池的时候触发 132 | 133 | 资源对象 134 | 135 | 136 | 137 | 检查可用性 138 | 139 | 资源对象 140 | 141 | 142 | 143 | 144 | 事件:可用时触发 145 | 146 | 147 | 148 | 149 | 事件:不可用时触发 150 | 151 | 152 | 153 | 154 | 所属对象池 155 | 156 | 157 | 158 | 159 | 在对象池中的唯一标识 160 | 161 | 162 | 163 | 164 | 资源对象 165 | 166 | 167 | 168 | 169 | 被获取的总次数 170 | 171 | 172 | 173 | 最后获取时的时间 174 | 175 | 176 | 177 | 最后归还时的时间 178 | 179 | 180 | 181 | 182 | 创建时间 183 | 184 | 185 | 186 | 187 | 最后获取时的线程id 188 | 189 | 190 | 191 | 192 | 最后归还时的线程id 193 | 194 | 195 | 196 | 197 | 重置 Value 值 198 | 199 | 200 | 201 | 202 | 对象池管理类 203 | 204 | 对象类型 205 | 206 | 207 | 208 | 后台定时检查可用性 209 | 210 | 211 | 212 | 213 | 214 | 创建对象池 215 | 216 | 池大小 217 | 池内对象的创建委托 218 | 获取池内对象成功后,进行使用前操作 219 | 220 | 221 | 222 | 创建对象池 223 | 224 | 策略 225 | 226 | 227 | 228 | 获取可用资源,或创建资源 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /SafeObjectPool/DefaultPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SafeObjectPool 5 | { 6 | 7 | public class DefaultPolicy : IPolicy 8 | { 9 | 10 | public string Name { get; set; } = typeof(DefaultPolicy).GetType().FullName; 11 | public int PoolSize { get; set; } = 1000; 12 | public TimeSpan SyncGetTimeout { get; set; } = TimeSpan.FromSeconds(10); 13 | public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromSeconds(50); 14 | public int AsyncGetCapacity { get; set; } = 10000; 15 | public bool IsThrowGetTimeoutException { get; set; } = true; 16 | public bool IsAutoDisposeWithSystem { get; set; } = true; 17 | public int CheckAvailableInterval { get; set; } = 3; 18 | 19 | public Func CreateObject; 20 | public Action> OnGetObject; 21 | 22 | public T OnCreate() 23 | { 24 | return CreateObject(); 25 | } 26 | 27 | public void OnDestroy(T obj) 28 | { 29 | 30 | } 31 | 32 | public void OnGet(Object obj) 33 | { 34 | OnGetObject?.Invoke(obj); 35 | } 36 | 37 | #if net40 38 | #else 39 | public Task OnGetAsync(Object obj) 40 | { 41 | OnGetObject?.Invoke(obj); 42 | return Task.FromResult(true); 43 | } 44 | #endif 45 | 46 | public void OnGetTimeout() 47 | { 48 | 49 | } 50 | 51 | public void OnReturn(Object obj) 52 | { 53 | } 54 | 55 | public bool OnCheckAvailable(Object obj) 56 | { 57 | return true; 58 | } 59 | 60 | public void OnAvailable() 61 | { 62 | 63 | } 64 | 65 | public void OnUnavailable() 66 | { 67 | 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /SafeObjectPool/IObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SafeObjectPool 5 | { 6 | public interface IObjectPool : IDisposable 7 | { 8 | IPolicy Policy { get; } 9 | /// 10 | /// 是否可用 11 | /// 12 | bool IsAvailable { get; } 13 | /// 14 | /// 不可用错误 15 | /// 16 | Exception UnavailableException { get; } 17 | /// 18 | /// 不可用时间 19 | /// 20 | DateTime? UnavailableTime { get; } 21 | 22 | /// 23 | /// 将对象池设置为不可用,后续 Get/GetAsync 均会报错,同时启动后台定时检查服务恢复可用 24 | /// 25 | /// 26 | /// 27 | /// 由【可用】变成【不可用】时返回true,否则返回false 28 | bool SetUnavailable(Exception exception, DateTime lastGetTime); 29 | 30 | /// 31 | /// 统计对象池中的对象 32 | /// 33 | string Statistics { get; } 34 | /// 35 | /// 统计对象池中的对象(完整) 36 | /// 37 | string StatisticsFullily { get; } 38 | 39 | /// 40 | /// 获取资源 41 | /// 42 | /// 超时 43 | /// 44 | Object Get(TimeSpan? timeout = null); 45 | 46 | #if net40 47 | #else 48 | /// 49 | /// 获取资源 50 | /// 51 | /// 52 | Task> GetAsync(); 53 | #endif 54 | 55 | /// 56 | /// 使用完毕后,归还资源 57 | /// 58 | /// 对象 59 | /// 是否重新创建 60 | void Return(Object obj, bool isReset = false); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SafeObjectPool/IPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SafeObjectPool 5 | { 6 | public interface IPolicy 7 | { 8 | 9 | /// 10 | /// 名称 11 | /// 12 | string Name { get; set; } 13 | 14 | /// 15 | /// 池容量 16 | /// 17 | int PoolSize { get; set; } 18 | 19 | /// 20 | /// 默认获取超时设置 21 | /// 22 | TimeSpan SyncGetTimeout { get; set; } 23 | 24 | /// 25 | /// 空闲时间,获取时若超出,则重新创建 26 | /// 27 | TimeSpan IdleTimeout { get; set; } 28 | 29 | /// 30 | /// 异步获取排队队列大小,小于等于0不生效 31 | /// 32 | int AsyncGetCapacity { get; set; } 33 | 34 | /// 35 | /// 获取超时后,是否抛出异常 36 | /// 37 | bool IsThrowGetTimeoutException { get; set; } 38 | 39 | /// 40 | /// 监听 AppDomain.CurrentDomain.ProcessExit/Console.CancelKeyPress 事件自动释放 41 | /// 42 | bool IsAutoDisposeWithSystem { get; set; } 43 | 44 | /// 45 | /// 后台定时检查可用性间隔秒数 46 | /// 47 | int CheckAvailableInterval { get; set; } 48 | 49 | /// 50 | /// 对象池的对象被创建时 51 | /// 52 | /// 返回被创建的对象 53 | T OnCreate(); 54 | 55 | /// 56 | /// 销毁对象 57 | /// 58 | /// 资源对象 59 | void OnDestroy(T obj); 60 | 61 | /// 62 | /// 从对象池获取对象超时的时候触发,通过该方法统计 63 | /// 64 | void OnGetTimeout(); 65 | 66 | /// 67 | /// 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 68 | /// 69 | /// 资源对象 70 | void OnGet(Object obj); 71 | #if net40 72 | #else 73 | /// 74 | /// 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 75 | /// 76 | /// 资源对象 77 | Task OnGetAsync(Object obj); 78 | #endif 79 | 80 | /// 81 | /// 归还对象给对象池的时候触发 82 | /// 83 | /// 资源对象 84 | void OnReturn(Object obj); 85 | 86 | /// 87 | /// 检查可用性 88 | /// 89 | /// 资源对象 90 | /// 91 | bool OnCheckAvailable(Object obj); 92 | 93 | /// 94 | /// 事件:可用时触发 95 | /// 96 | void OnAvailable(); 97 | /// 98 | /// 事件:不可用时触发 99 | /// 100 | void OnUnavailable(); 101 | } 102 | } -------------------------------------------------------------------------------- /SafeObjectPool/Object.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace SafeObjectPool 5 | { 6 | 7 | public class Object : IDisposable 8 | { 9 | public static Object InitWith(IObjectPool pool, int id, T value) 10 | { 11 | return new Object 12 | { 13 | Pool = pool, 14 | Id = id, 15 | Value = value, 16 | LastGetThreadId = Thread.CurrentThread.ManagedThreadId, 17 | LastGetTime = DateTime.Now, 18 | LastGetTimeCopy = DateTime.Now 19 | }; 20 | } 21 | 22 | /// 23 | /// 所属对象池 24 | /// 25 | public IObjectPool Pool { get; internal set; } 26 | 27 | /// 28 | /// 在对象池中的唯一标识 29 | /// 30 | public int Id { get; internal set; } 31 | /// 32 | /// 资源对象 33 | /// 34 | public T Value { get; internal set; } 35 | 36 | internal long _getTimes; 37 | /// 38 | /// 被获取的总次数 39 | /// 40 | public long GetTimes => _getTimes; 41 | 42 | /// 最后获取时的时间 43 | public DateTime LastGetTime { get; internal set; } 44 | public DateTime LastGetTimeCopy { get; internal set; } 45 | 46 | /// 47 | /// 最后归还时的时间 48 | /// 49 | public DateTime LastReturnTime { get; internal set; } 50 | 51 | /// 52 | /// 创建时间 53 | /// 54 | public DateTime CreateTime { get; internal set; } = DateTime.Now; 55 | 56 | /// 57 | /// 最后获取时的线程id 58 | /// 59 | public int LastGetThreadId { get; internal set; } 60 | 61 | /// 62 | /// 最后归还时的线程id 63 | /// 64 | public int LastReturnThreadId { get; internal set; } 65 | 66 | public override string ToString() 67 | { 68 | return $"{this.Value}, Times: {this.GetTimes}, ThreadId(R/G): {this.LastReturnThreadId}/{this.LastGetThreadId}, Time(R/G): {this.LastReturnTime.ToString("yyyy-MM-dd HH:mm:ss:ms")}/{this.LastGetTime.ToString("yyyy-MM-dd HH:mm:ss:ms")}"; 69 | } 70 | 71 | /// 72 | /// 重置 Value 值 73 | /// 74 | public void ResetValue() 75 | { 76 | if (this.Value != null) 77 | { 78 | try { this.Pool.Policy.OnDestroy(this.Value); } catch { } 79 | try { (this.Value as IDisposable)?.Dispose(); } catch { } 80 | } 81 | T value = default(T); 82 | try { value = this.Pool.Policy.OnCreate(); } catch { } 83 | this.Value = value; 84 | this.LastReturnTime = DateTime.Now; 85 | } 86 | 87 | internal bool _isReturned = false; 88 | public void Dispose() 89 | { 90 | Pool?.Return(this); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /SafeObjectPool/ObjectPool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace SafeObjectPool 9 | { 10 | internal class TestTrace 11 | { 12 | internal static void WriteLine(string text, ConsoleColor backgroundColor) 13 | { 14 | try //#643 15 | { 16 | var bgcolor = Console.BackgroundColor; 17 | var forecolor = Console.ForegroundColor; 18 | Console.BackgroundColor = backgroundColor; 19 | 20 | switch (backgroundColor) 21 | { 22 | case ConsoleColor.Yellow: 23 | Console.ForegroundColor = ConsoleColor.White; 24 | break; 25 | case ConsoleColor.DarkGreen: 26 | Console.ForegroundColor = ConsoleColor.White; 27 | break; 28 | } 29 | Console.Write(text); 30 | Console.BackgroundColor = bgcolor; 31 | Console.ForegroundColor = forecolor; 32 | Console.WriteLine(); 33 | } 34 | catch 35 | { 36 | try 37 | { 38 | System.Diagnostics.Debug.WriteLine(text); 39 | } 40 | catch { } 41 | } 42 | } 43 | } 44 | 45 | /// 46 | /// 对象池管理类 47 | /// 48 | /// 对象类型 49 | public partial class ObjectPool : IObjectPool 50 | { 51 | public IPolicy Policy { get; protected set; } 52 | 53 | private object _allObjectsLock = new object(); 54 | internal List> _allObjects = new List>(); 55 | internal ConcurrentStack> _freeObjects = new ConcurrentStack>(); 56 | 57 | private ConcurrentQueue _getSyncQueue = new ConcurrentQueue(); 58 | private ConcurrentQueue>> _getAsyncQueue = new ConcurrentQueue>>(); 59 | private ConcurrentQueue _getQueue = new ConcurrentQueue(); 60 | 61 | public bool IsAvailable => this.UnavailableException == null; 62 | public Exception UnavailableException { get; private set; } 63 | public DateTime? UnavailableTime { get; private set; } 64 | public DateTime? AvailableTime { get; private set; } 65 | private object UnavailableLock = new object(); 66 | private bool running = true; 67 | 68 | public bool SetUnavailable(Exception exception, DateTime lastGetTime) 69 | { 70 | bool isseted = false; 71 | if (exception != null && UnavailableException == null) 72 | { 73 | lock (UnavailableLock) 74 | { 75 | if (UnavailableException == null) 76 | { 77 | if (lastGetTime < AvailableTime) return false; //已经恢复 78 | UnavailableException = exception; 79 | UnavailableTime = DateTime.Now; 80 | AvailableTime = null; 81 | isseted = true; 82 | } 83 | } 84 | } 85 | 86 | if (isseted) 87 | { 88 | Policy.OnUnavailable(); 89 | CheckAvailable(Policy.CheckAvailableInterval); 90 | } 91 | 92 | return isseted; 93 | } 94 | 95 | /// 96 | /// 后台定时检查可用性 97 | /// 98 | /// 99 | private void CheckAvailable(int interval) 100 | { 101 | new Thread(() => 102 | { 103 | if (UnavailableException != null) 104 | TestTrace.WriteLine($"【{Policy.Name}】Next recovery time:{DateTime.Now.AddSeconds(interval)}", ConsoleColor.DarkYellow); 105 | 106 | while (UnavailableException != null) 107 | { 108 | if (running == false) return; 109 | Thread.CurrentThread.Join(TimeSpan.FromSeconds(interval)); 110 | if (running == false) return; 111 | 112 | try 113 | { 114 | var conn = GetFree(false); 115 | if (conn == null) throw new Exception($"【{Policy.Name}】Failed to get resource {this.Statistics}"); 116 | 117 | try 118 | { 119 | try 120 | { 121 | Policy.OnCheckAvailable(conn); 122 | break; 123 | } 124 | catch 125 | { 126 | conn.ResetValue(); 127 | } 128 | if (Policy.OnCheckAvailable(conn) == false) throw new Exception($"【{Policy.Name}】An exception needs to be thrown"); 129 | break; 130 | } 131 | finally 132 | { 133 | Return(conn); 134 | } 135 | } 136 | catch (Exception ex) 137 | { 138 | TestTrace.WriteLine($"【{Policy.Name}】Next recovery time: {DateTime.Now.AddSeconds(interval)} ({ex.Message})", ConsoleColor.DarkYellow); 139 | } 140 | } 141 | 142 | RestoreToAvailable(); 143 | 144 | }).Start(); 145 | } 146 | 147 | private void RestoreToAvailable() 148 | { 149 | 150 | bool isRestored = false; 151 | if (UnavailableException != null) 152 | { 153 | lock (UnavailableLock) 154 | { 155 | if (UnavailableException != null) 156 | { 157 | lock (_allObjectsLock) 158 | _allObjects.ForEach(a => a.LastGetTime = a.LastReturnTime = new DateTime(2000, 1, 1)); 159 | UnavailableException = null; 160 | UnavailableTime = null; 161 | AvailableTime = DateTime.Now; 162 | isRestored = true; 163 | } 164 | } 165 | } 166 | 167 | if (isRestored) 168 | { 169 | Policy.OnAvailable(); 170 | TestTrace.WriteLine($"【{Policy.Name}】Recovered", ConsoleColor.DarkGreen); 171 | } 172 | } 173 | 174 | protected bool LiveCheckAvailable() 175 | { 176 | try 177 | { 178 | var conn = GetFree(false); 179 | if (conn == null) throw new Exception($"【{Policy.Name}】Failed to get resource {this.Statistics}"); 180 | 181 | try 182 | { 183 | if (Policy.OnCheckAvailable(conn) == false) throw new Exception("【{Policy.Name}】An exception needs to be thrown"); 184 | } 185 | finally 186 | { 187 | Return(conn); 188 | } 189 | } 190 | catch 191 | { 192 | return false; 193 | } 194 | 195 | RestoreToAvailable(); 196 | return true; 197 | } 198 | 199 | public string Statistics => $"Pool: {_freeObjects.Count}/{_allObjects.Count}, Get wait: {_getSyncQueue.Count}, GetAsync wait: {_getAsyncQueue.Count}"; 200 | public string StatisticsFullily 201 | { 202 | get 203 | { 204 | var sb = new StringBuilder(); 205 | 206 | sb.AppendLine(Statistics); 207 | sb.AppendLine(""); 208 | 209 | foreach (var obj in _allObjects) 210 | { 211 | sb.AppendLine($"{obj.Value}, Times: {obj.GetTimes}, ThreadId(R/G): {obj.LastReturnThreadId}/{obj.LastGetThreadId}, Time(R/G): {obj.LastReturnTime.ToString("yyyy-MM-dd HH:mm:ss:ms")}/{obj.LastGetTime.ToString("yyyy-MM-dd HH:mm:ss:ms")}, "); 212 | } 213 | 214 | return sb.ToString(); 215 | } 216 | } 217 | 218 | /// 219 | /// 创建对象池 220 | /// 221 | /// 池大小 222 | /// 池内对象的创建委托 223 | /// 获取池内对象成功后,进行使用前操作 224 | public ObjectPool(int poolsize, Func createObject, Action> onGetObject = null) : this(new DefaultPolicy { PoolSize = poolsize, CreateObject = createObject, OnGetObject = onGetObject }) 225 | { 226 | } 227 | /// 228 | /// 创建对象池 229 | /// 230 | /// 策略 231 | public ObjectPool(IPolicy policy) 232 | { 233 | Policy = policy; 234 | 235 | AppDomain.CurrentDomain.ProcessExit += (s1, e1) => 236 | { 237 | if (Policy.IsAutoDisposeWithSystem) 238 | running = false; 239 | }; 240 | try 241 | { 242 | Console.CancelKeyPress += (s1, e1) => 243 | { 244 | if (e1.Cancel) return; 245 | if (Policy.IsAutoDisposeWithSystem) 246 | running = false; 247 | }; 248 | } 249 | catch { } 250 | } 251 | 252 | public void AutoFree() 253 | { 254 | if (running == false) return; 255 | if (UnavailableException != null) return; 256 | 257 | var list = new List>(); 258 | while (_freeObjects.TryPop(out var obj)) 259 | list.Add(obj); 260 | foreach (var obj in list) 261 | { 262 | if (obj != null && obj.Value == null || 263 | obj != null && Policy.IdleTimeout > TimeSpan.Zero && DateTime.Now.Subtract(obj.LastReturnTime) > Policy.IdleTimeout) 264 | { 265 | if (obj.Value != null) 266 | { 267 | Return(obj, true); 268 | continue; 269 | } 270 | } 271 | Return(obj); 272 | } 273 | } 274 | 275 | /// 276 | /// 获取可用资源,或创建资源 277 | /// 278 | /// 279 | private Object GetFree(bool checkAvailable) 280 | { 281 | 282 | if (running == false) 283 | throw new ObjectDisposedException($"【{Policy.Name}】The ObjectPool has been disposed, see: https://github.com/dotnetcore/FreeSql/discussions/1079"); 284 | 285 | if (checkAvailable && UnavailableException != null) 286 | throw new Exception($"【{Policy.Name}】Status unavailable, waiting for recovery. {UnavailableException?.Message}", UnavailableException); 287 | 288 | if ((_freeObjects.TryPop(out var obj) == false || obj == null) && _allObjects.Count < Policy.PoolSize) 289 | { 290 | lock (_allObjectsLock) 291 | if (_allObjects.Count < Policy.PoolSize) 292 | _allObjects.Add(obj = new Object { Pool = this, Id = _allObjects.Count + 1 }); 293 | } 294 | 295 | if (obj != null) 296 | obj._isReturned = false; 297 | 298 | if (obj != null && obj.Value == null || 299 | obj != null && Policy.IdleTimeout > TimeSpan.Zero && DateTime.Now.Subtract(obj.LastReturnTime) > Policy.IdleTimeout) 300 | { 301 | try 302 | { 303 | obj.ResetValue(); 304 | } 305 | catch 306 | { 307 | Return(obj); 308 | throw; 309 | } 310 | } 311 | 312 | return obj; 313 | } 314 | 315 | public Object Get(TimeSpan? timeout = null) 316 | { 317 | var obj = GetFree(true); 318 | if (obj == null) 319 | { 320 | var queueItem = new GetSyncQueueInfo(); 321 | 322 | _getSyncQueue.Enqueue(queueItem); 323 | _getQueue.Enqueue(false); 324 | 325 | if (timeout == null) timeout = Policy.SyncGetTimeout; 326 | 327 | try 328 | { 329 | if (queueItem.Wait.Wait(timeout.Value)) 330 | obj = queueItem.ReturnValue; 331 | } 332 | catch { } 333 | 334 | if (obj == null) obj = queueItem.ReturnValue; 335 | if (obj == null) lock (queueItem.Lock) queueItem.IsTimeout = (obj = queueItem.ReturnValue) == null; 336 | if (obj == null) obj = queueItem.ReturnValue; 337 | if (queueItem.Exception != null) throw queueItem.Exception; 338 | 339 | if (obj == null) 340 | { 341 | Policy.OnGetTimeout(); 342 | if (Policy.IsThrowGetTimeoutException) 343 | throw new TimeoutException($"【{Policy.Name}】ObjectPool.Get() timeout {timeout.Value.TotalSeconds} seconds, see: https://github.com/dotnetcore/FreeSql/discussions/1081"); 344 | 345 | return null; 346 | } 347 | } 348 | 349 | try 350 | { 351 | Policy.OnGet(obj); 352 | } 353 | catch 354 | { 355 | Return(obj); 356 | throw; 357 | } 358 | 359 | obj.LastGetThreadId = Thread.CurrentThread.ManagedThreadId; 360 | obj.LastGetTime = DateTime.Now; 361 | obj.LastGetTimeCopy = DateTime.Now; 362 | Interlocked.Increment(ref obj._getTimes); 363 | 364 | return obj; 365 | } 366 | 367 | #if net40 368 | #else 369 | async public Task> GetAsync() 370 | { 371 | var obj = GetFree(true); 372 | if (obj == null) 373 | { 374 | if (Policy.AsyncGetCapacity > 0 && _getAsyncQueue.Count >= Policy.AsyncGetCapacity - 1) 375 | throw new OutOfMemoryException($"【{Policy.Name}】ObjectPool.GetAsync() The queue is too long. Policy.AsyncGetCapacity = {Policy.AsyncGetCapacity}"); 376 | 377 | var tcs = new TaskCompletionSource>(); 378 | 379 | _getAsyncQueue.Enqueue(tcs); 380 | _getQueue.Enqueue(true); 381 | 382 | obj = await tcs.Task; 383 | 384 | //if (timeout == null) timeout = Policy.SyncGetTimeout; 385 | 386 | //if (tcs.Task.Wait(timeout.Value)) 387 | // obj = tcs.Task.Result; 388 | 389 | //if (obj == null) { 390 | 391 | // tcs.TrySetCanceled(); 392 | // Policy.GetTimeout(); 393 | 394 | // if (Policy.IsThrowGetTimeoutException) 395 | // throw new TimeoutException($"【{Policy.Name}】ObjectPool.GetAsync() timeout {timeout.Value.TotalSeconds} seconds, see: https://github.com/dotnetcore/FreeSql/discussions/1081"); 396 | 397 | // return null; 398 | //} 399 | } 400 | 401 | try 402 | { 403 | await Policy.OnGetAsync(obj); 404 | } 405 | catch 406 | { 407 | Return(obj); 408 | throw; 409 | } 410 | 411 | obj.LastGetThreadId = Thread.CurrentThread.ManagedThreadId; 412 | obj.LastGetTime = DateTime.Now; 413 | obj.LastGetTimeCopy = DateTime.Now; 414 | Interlocked.Increment(ref obj._getTimes); 415 | 416 | return obj; 417 | } 418 | #endif 419 | 420 | public void Return(Object obj, bool isReset = false) 421 | { 422 | if (obj == null) return; 423 | if (obj._isReturned) return; 424 | 425 | if (running == false) 426 | { 427 | Policy.OnDestroy(obj.Value); 428 | try { (obj.Value as IDisposable)?.Dispose(); } catch { } 429 | return; 430 | } 431 | 432 | if (isReset) obj.ResetValue(); 433 | bool isReturn = false; 434 | 435 | while (isReturn == false && _getQueue.TryDequeue(out var isAsync)) 436 | { 437 | if (isAsync == false) 438 | { 439 | if (_getSyncQueue.TryDequeue(out var queueItem) && queueItem != null) 440 | { 441 | lock (queueItem.Lock) 442 | if (queueItem.IsTimeout == false) 443 | queueItem.ReturnValue = obj; 444 | 445 | if (queueItem.ReturnValue != null) 446 | { 447 | if (UnavailableException != null) 448 | { 449 | queueItem.Exception = new Exception($"【{Policy.Name}】Status unavailable, waiting for recovery. {UnavailableException?.Message}", UnavailableException); 450 | try 451 | { 452 | queueItem.Wait.Set(); 453 | } 454 | catch { } 455 | } 456 | else 457 | { 458 | obj.LastReturnThreadId = Thread.CurrentThread.ManagedThreadId; 459 | obj.LastReturnTime = DateTime.Now; 460 | 461 | try 462 | { 463 | queueItem.Wait.Set(); 464 | isReturn = true; 465 | } 466 | catch { } 467 | } 468 | } 469 | 470 | try { queueItem.Dispose(); } catch { } 471 | } 472 | } 473 | else 474 | { 475 | if (_getAsyncQueue.TryDequeue(out var tcs) && tcs != null && tcs.Task.IsCanceled == false) 476 | { 477 | if (UnavailableException != null) 478 | { 479 | try 480 | { 481 | tcs.TrySetException(new Exception($"【{Policy.Name}】Status unavailable, waiting for recovery. {UnavailableException?.Message}", UnavailableException)); 482 | } 483 | catch { } 484 | } 485 | else 486 | { 487 | obj.LastReturnThreadId = Thread.CurrentThread.ManagedThreadId; 488 | obj.LastReturnTime = DateTime.Now; 489 | 490 | try 491 | { 492 | isReturn = tcs.TrySetResult(obj); 493 | } 494 | catch { } 495 | } 496 | } 497 | } 498 | } 499 | 500 | //无排队,直接归还 501 | if (isReturn == false) 502 | { 503 | try 504 | { 505 | Policy.OnReturn(obj); 506 | } 507 | catch 508 | { 509 | throw; 510 | } 511 | finally 512 | { 513 | obj.LastReturnThreadId = Thread.CurrentThread.ManagedThreadId; 514 | obj.LastReturnTime = DateTime.Now; 515 | obj._isReturned = true; 516 | 517 | _freeObjects.Push(obj); 518 | } 519 | } 520 | } 521 | 522 | public void Dispose() 523 | { 524 | running = false; 525 | 526 | while (_freeObjects.TryPop(out var fo)) ; 527 | while (_getSyncQueue.TryDequeue(out var sync)) 528 | { 529 | try { sync.Wait.Set(); } catch { } 530 | } 531 | 532 | while (_getAsyncQueue.TryDequeue(out var async)) 533 | async.TrySetCanceled(); 534 | 535 | while (_getQueue.TryDequeue(out var qs)) ; 536 | 537 | for (var a = 0; a < _allObjects.Count; a++) 538 | { 539 | Policy.OnDestroy(_allObjects[a].Value); 540 | try { (_allObjects[a].Value as IDisposable)?.Dispose(); } catch { } 541 | } 542 | 543 | _allObjects.Clear(); 544 | } 545 | 546 | class GetSyncQueueInfo : IDisposable 547 | { 548 | internal ManualResetEventSlim Wait { get; set; } = new ManualResetEventSlim(); 549 | internal Object ReturnValue { get; set; } 550 | internal object Lock = new object(); 551 | internal bool IsTimeout { get; set; } = false; 552 | internal Exception Exception { get; set; } 553 | 554 | public void Dispose() 555 | { 556 | try 557 | { 558 | if (Wait != null) 559 | Wait.Dispose(); 560 | } 561 | catch 562 | { 563 | } 564 | } 565 | } 566 | } 567 | } -------------------------------------------------------------------------------- /key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2881099/SafeObjectPool/ab2de1867be9fba7db6acd6f4fcd99e5df9ac241/key.snk --------------------------------------------------------------------------------