mirror of
https://gitee.com/LongbowEnterprise/BootstrapBlazor.git
synced 2025-12-06 07:28:50 +08:00
feat(ICacheManager): add TryGetCacheEntry method (#5216)
* doc: 增加到期时间列 * test: 增加 benmark 测试功能 * feat: 增加 TryGetCacheEntry 方法 * doc: 增加过期时间 * test: 更新单元测试 * test: 更新单元测试
This commit is contained in:
@@ -74,6 +74,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cert", "cert", "{C075C6C8-B
|
||||
scripts\linux\cert\www.blazor.zone.key = scripts\linux\cert\www.blazor.zone.key
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{9BAF50BE-141D-4429-93A9-942F373D1F68}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTest.Benchmarks", "tools\Benchmarks\UnitTest.Benchmarks.csproj", "{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -104,6 +108,10 @@ Global
|
||||
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -119,6 +127,7 @@ Global
|
||||
{6D73FED6-0086-460B-84FA-1FA78176BF59} = {7C1D79F1-87BC-42C1-BD5A-CDE4044AC1BD}
|
||||
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF} = {7C1D79F1-87BC-42C1-BD5A-CDE4044AC1BD}
|
||||
{C075C6C8-B9CB-4AC0-9BDF-B2002B4AB99C} = {EA765165-0542-41C8-93F2-85787FEDEDFF}
|
||||
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1} = {9BAF50BE-141D-4429-93A9-942F373D1F68}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0DCB0756-34FA-4FD0-AE1D-D3F08B5B3A6B}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@ExpirationTime
|
||||
@@ -0,0 +1,62 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BootstrapBlazor.Server.Components.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// CacaheExpiration 组件
|
||||
/// </summary>
|
||||
public partial class CacaheExpiration
|
||||
{
|
||||
[Inject, NotNull]
|
||||
private ICacheManager? CacheManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 <see cref="TableColumnContext{TItem, TValue}"/> 实例
|
||||
/// </summary>
|
||||
[Parameter, NotNull]
|
||||
public object? Key { get; set; }
|
||||
|
||||
private string? ExpirationTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
await base.OnParametersSetAsync();
|
||||
|
||||
await GetCacheEntryExpiration();
|
||||
}
|
||||
|
||||
private async Task GetCacheEntryExpiration()
|
||||
{
|
||||
ExpirationTime = "loading ...";
|
||||
await Task.Yield();
|
||||
|
||||
if (CacheManager.TryGetCacheEntry(Key, out ICacheEntry? entry))
|
||||
{
|
||||
if (entry.Priority == CacheItemPriority.NeverRemove)
|
||||
{
|
||||
ExpirationTime = "Never Remove";
|
||||
}
|
||||
else if (entry.SlidingExpiration.HasValue)
|
||||
{
|
||||
ExpirationTime = $"Sliding: {entry.SlidingExpiration.Value}";
|
||||
}
|
||||
else if (entry.AbsoluteExpiration.HasValue)
|
||||
{
|
||||
ExpirationTime = $"Absolute: {entry.AbsoluteExpiration.Value}";
|
||||
}
|
||||
else if (entry.ExpirationTokens.Count != 0)
|
||||
{
|
||||
ExpirationTime = $"Token: {entry.ExpirationTokens.Count}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@
|
||||
@GetValue(v.Row)
|
||||
</Template>
|
||||
</TableTemplateColumn>
|
||||
<TableTemplateColumn Text="@Localizer["CacheListExpiration"]" Width="160">
|
||||
<Template Context="v">
|
||||
<CacaheExpiration Key="v.Row"></CacaheExpiration>
|
||||
</Template>
|
||||
</TableTemplateColumn>
|
||||
<TableTemplateColumn Text="@Localizer["CacheListAction"]" Width="80">
|
||||
<Template Context="v">
|
||||
<Button Size="Size.ExtraSmall" Color="Color.Danger" OnClick="() => OnDelete(v.Row)" Icon="fa-solid fa-xmark" Text="@Localizer["CacheListDelete"]"></Button>
|
||||
|
||||
@@ -49,7 +49,7 @@ public partial class CacheList
|
||||
|
||||
private void UpdateCacheList()
|
||||
{
|
||||
_cacheList = CacheManager.Keys.OrderBy(i => i.ToString()).ToList();
|
||||
_cacheList = [.. CacheManager.Keys.OrderBy(i => i.ToString())];
|
||||
}
|
||||
|
||||
private string GetValue(object key)
|
||||
|
||||
@@ -6910,6 +6910,7 @@
|
||||
"CacheListIntro": "Manage the component library internal cache through the <code>ICacheManager</code> interface method",
|
||||
"CacheListKey": "Key",
|
||||
"CacheListValue": "Value",
|
||||
"CacheListExpiration": "Expiration",
|
||||
"CacheListAction": "Action",
|
||||
"CacheListRefresh": "Refresh",
|
||||
"CacheListDelete": "Delete",
|
||||
|
||||
@@ -6910,6 +6910,7 @@
|
||||
"CacheListIntro": "通过 <code>ICacheManager</code> 接口方法管理组件库内部缓存",
|
||||
"CacheListKey": "键",
|
||||
"CacheListValue": "值",
|
||||
"CacheListExpiration": "到期时间",
|
||||
"CacheListAction": "操作",
|
||||
"CacheListRefresh": "刷新",
|
||||
"CacheListDelete": "删除",
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
#if NET8_0_OR_GREATER
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Frozen;
|
||||
#endif
|
||||
|
||||
@@ -172,6 +173,53 @@ internal class CacheManager : ICacheManager
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
private object? _coherentStateInstance = null;
|
||||
|
||||
private MethodInfo? _allValuesMethodInfo = null;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="entry"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetCacheEntry(object? key, [NotNullWhen(true)] out ICacheEntry? entry)
|
||||
{
|
||||
entry = null;
|
||||
if (key == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Cache is MemoryCache cache)
|
||||
{
|
||||
var values = GetAllValues(cache);
|
||||
entry = values.Find(e => e.Key == key);
|
||||
}
|
||||
return entry != null;
|
||||
}
|
||||
|
||||
private static object GetCoherentState(MemoryCache cache)
|
||||
{
|
||||
var fieldInfo = cache.GetType().GetField("_coherentState", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
return fieldInfo.GetValue(cache)!;
|
||||
}
|
||||
|
||||
private static MethodInfo GetAllValuesMethodInfo(object coherentStateInstance) => coherentStateInstance.GetType().GetMethod("GetAllValues", BindingFlags.Instance | BindingFlags.Public)!;
|
||||
|
||||
private List<ICacheEntry> GetAllValues(MemoryCache cache)
|
||||
{
|
||||
_coherentStateInstance ??= GetCoherentState(cache);
|
||||
_allValuesMethodInfo ??= GetAllValuesMethodInfo(_coherentStateInstance);
|
||||
|
||||
var ret = new List<ICacheEntry>();
|
||||
if (_allValuesMethodInfo.Invoke(_coherentStateInstance, null) is IEnumerable<ICacheEntry> values)
|
||||
{
|
||||
ret.AddRange(values);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#region Count
|
||||
|
||||
@@ -66,5 +66,13 @@ public interface ICacheManager
|
||||
/// 获得 缓存键集合
|
||||
/// </summary>
|
||||
IEnumerable<object> Keys { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 通过指定 key 获取缓存项 <see cref="ICacheEntry"/> 实例
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="entry"></param>
|
||||
/// <returns></returns>
|
||||
bool TryGetCacheEntry(object? key, [NotNullWhen(true)] out ICacheEntry? entry);
|
||||
#endif
|
||||
}
|
||||
|
||||
41
test/UnitTest/Performance/UnsafeAccessorTest.cs
Normal file
41
test/UnitTest/Performance/UnsafeAccessorTest.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnitTest.Performance;
|
||||
|
||||
public class UnsafeAccessorTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetField_Ok()
|
||||
{
|
||||
var dummy = new Dummy();
|
||||
dummy.SetName("test");
|
||||
Assert.Equal("test", GetNameField(dummy));
|
||||
}
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_name")]
|
||||
static extern ref string GetNameField(Dummy @this);
|
||||
|
||||
private class Dummy
|
||||
{
|
||||
private string? _name;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string? GetName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public void SetName(string? name)
|
||||
{
|
||||
_name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,4 +146,18 @@ public class CacheManagerTest : BootstrapBlazorTestBase
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetCacheEntry()
|
||||
{
|
||||
Cache.GetOrCreate("test_01", entry =>
|
||||
{
|
||||
return 1;
|
||||
});
|
||||
Assert.True(Cache.TryGetCacheEntry("test_01", out var entry));
|
||||
Assert.NotNull(entry);
|
||||
|
||||
Assert.False(Cache.TryGetCacheEntry(null, out var v));
|
||||
Assert.Null(v);
|
||||
}
|
||||
}
|
||||
|
||||
62
tools/Benchmarks/Benmarks.cs
Normal file
62
tools/Benchmarks/Benmarks.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using Microsoft.Diagnostics.Runtime.Utilities;
|
||||
using System.Dynamic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnitTest.Benchmarks;
|
||||
|
||||
[SimpleJob(RuntimeMoniker.Net90)]
|
||||
public class Benchmarks
|
||||
{
|
||||
static readonly Foo _instance = new();
|
||||
|
||||
static readonly FieldInfo _privateField = typeof(Foo).GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_name")]
|
||||
static extern ref string GetNameValue(Foo @this);
|
||||
|
||||
[Benchmark]
|
||||
public object? Reflection()
|
||||
{
|
||||
var fieldInfo = typeof(Foo).GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
return fieldInfo.GetValue(_instance);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public object? ReflectionWithCache() => _privateField.GetValue(_instance);
|
||||
|
||||
[Benchmark]
|
||||
public string UnsafeAccessor() => GetNameValue(_instance);
|
||||
|
||||
[Benchmark]
|
||||
public string DirectAccess() => _instance.GetName();
|
||||
|
||||
[Benchmark]
|
||||
public string Lambda() => GetFieldValue();
|
||||
|
||||
[Benchmark]
|
||||
public string LambdaWithCache() => GetFooFieldFunc(_instance);
|
||||
|
||||
public string GetFieldValue()
|
||||
{
|
||||
var method = GetFooFieldExpression().Compile();
|
||||
return method.Invoke(_instance);
|
||||
}
|
||||
|
||||
private static Func<Foo, string> GetFooFieldFunc = GetFooFieldExpression().Compile();
|
||||
|
||||
static Expression<Func<Foo, string>> GetFooFieldExpression()
|
||||
{
|
||||
var param_p1 = Expression.Parameter(typeof(Foo));
|
||||
var body = Expression.Field(param_p1, _privateField);
|
||||
return Expression.Lambda<Func<Foo, string>>(Expression.Convert(body, typeof(string)), param_p1);
|
||||
}
|
||||
}
|
||||
13
tools/Benchmarks/Foo.cs
Normal file
13
tools/Benchmarks/Foo.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
namespace UnitTest.Benchmarks;
|
||||
|
||||
public class Foo
|
||||
{
|
||||
private string _name = "test";
|
||||
|
||||
public string GetName() => _name;
|
||||
}
|
||||
11
tools/Benchmarks/Program.cs
Normal file
11
tools/Benchmarks/Program.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using BenchmarkDotNet.Running;
|
||||
using UnitTest.Benchmarks;
|
||||
|
||||
BenchmarkRunner.Run<Benchmarks>();
|
||||
|
||||
Console.ReadKey();
|
||||
14
tools/Benchmarks/UnitTest.Benchmarks.csproj
Normal file
14
tools/Benchmarks/UnitTest.Benchmarks.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user