- 缓存在内存中 ASP.NET CoreCache in-memory in ASP.NET Core
- 缓存基础知识Caching basics
- System.Runtime.Caching/MemoryCacheSystem.Runtime.Caching/MemoryCache
- 缓存指南Cache guidelines
- 使用 IMemoryCacheUse IMemoryCache
- MemoryCacheEntryOptionsMemoryCacheEntryOptions
- 使用 SetSize、Size 和 SizeLimit 限制缓存大小Use SetSize, Size, and SizeLimit to limit cache size
- 缓存依赖关系Cache dependencies
- 附加说明Additional notes
- 其他资源Additional resources
- 缓存基础知识Caching basics
- System.Runtime.Caching/MemoryCacheSystem.Runtime.Caching/MemoryCache
- 缓存指南Cache guidelines
- 使用 IMemoryCacheUsing IMemoryCache
- MemoryCacheEntryOptionsMemoryCacheEntryOptions
- 使用 SetSize、Size 和 SizeLimit 限制缓存大小Use SetSize, Size, and SizeLimit to limit cache size
- 缓存依赖关系Cache dependencies
- 附加说明Additional notes
- 其他资源Additional resources
缓存在内存中 ASP.NET CoreCache in-memory in ASP.NET Core
本文内容
作者: Rick Anderson、 John Luo和Steve Smith
缓存基础知识Caching basics
通过减少生成内容所需的工作,缓存可以显著提高应用的性能和可伸缩性。缓存最适用于不经常更改的数据,生成成本很高。通过缓存,可以比从数据源返回的数据的副本速度快得多。应该对应用进行编写和测试,使其永不依赖于缓存的数据。
ASP.NET Core 支持多种不同的缓存。最简单的缓存基于IMemoryCache。IMemoryCache
表示存储在 web 服务器的内存中的缓存。使用内存中缓存时,在服务器场(多台服务器)上运行的应用应确保会话是粘滞的。粘性会话可确保来自客户端的后续请求都转到同一台服务器。例如,Azure Web 应用使用应用程序请求路由(ARR)将所有后续请求路由到同一服务器。
Web 场中的非粘滞会话需要分布式缓存,以避免缓存一致性问题。对于某些应用,分布式缓存可支持比内存中缓存更高的向外扩展。使用分布式缓存可将缓存内存卸载到外部进程。
内存中缓存可以存储任何对象。分布式缓存接口仅限 byte[]
。内存中和分布式缓存将缓存项作为键值对。
System.Runtime.Caching/MemoryCacheSystem.Runtime.Caching/MemoryCache
System.Runtime.Caching/MemoryCache (NuGet 包)可用于:
- .NET Standard 2.0 或更高版本。
- 面向 .NET Standard 2.0 或更高版本的任何.net 实现。例如,ASP.NET Core 2.0 或更高版本。
- .NET Framework 4.5 或更高版本。
/System.Runtime.Caching
建议将/IMemoryCache
(本文所述)的(本文中介绍),因为它更好地集成到 MemoryCache
中。例如,IMemoryCache
与 ASP.NET Core依赖关系注入一起使用。
将 ASP.NET 4.x 中的代码移植到 ASP.NET Core 时,使用 System.Runtime.Caching
/MemoryCache
作为兼容性桥。
缓存指南Cache guidelines
- 代码应始终具有回退选项,以获取数据,而不是依赖于可用的缓存值。
- 缓存使用稀有资源内存。限制缓存增长:
- 不要使用外部输入作为缓存键。
- 使用过期限制缓存增长。
- 使用 SetSize、Size 和 SizeLimit 限制缓存大小。ASP.NET Core 运行时不会根据内存压力限制缓存大小。开发人员需要限制缓存大小。
使用 IMemoryCacheUse IMemoryCache
警告
使用依赖关系注入中的共享内存缓存并调用 SetSize
、Size
或 SizeLimit
来限制缓存大小可能会导致应用程序失败。在缓存上设置大小限制时,在添加时,所有项都必须指定大小。这可能会导致问题,因为开发人员可能无法完全控制使用共享缓存的内容。例如,Entity Framework Core 使用共享缓存并且未指定大小。如果应用设置了缓存大小限制并使用 EF Core,则应用将引发 InvalidOperationException
。使用 SetSize
、Size
或 SizeLimit
限制缓存时,为缓存创建一个缓存单独。有关详细信息和示例,请参阅使用 SetSize、Size 和 SizeLimit 限制缓存大小。共享缓存由其他框架或库共享。例如,EF Core 使用共享缓存并且未指定大小。
内存中缓存是从应用程序中使用依赖关系注入引用的一种服务。请求构造函数中的 IMemoryCache
实例:
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
以下代码使用TryGetValue来检查缓存中是否有时间。如果未缓存时间,则将创建一个新条目,并将其设置为已添加到缓存中。CacheKeys
类是下载示例的一部分。
public static class CacheKeys
{
public static string Entry { get { return "_Entry"; } }
public static string CallbackEntry { get { return "_Callback"; } }
public static string CallbackMessage { get { return "_CallbackMessage"; } }
public static string Parent { get { return "_Parent"; } }
public static string Child { get { return "_Child"; } }
public static string DependentMessage { get { return "_DependentMessage"; } }
public static string DependentCTS { get { return "_DependentCTS"; } }
public static string Ticks { get { return "_Ticks"; } }
public static string CancelMsg { get { return "_CancelMsg"; } }
public static string CancelTokenSource { get { return "_CancelTokenSource"; } }
}
public IActionResult CacheTryGetValueSet()
{
DateTime cacheEntry;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Save data in cache.
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
将会显示当前时间和缓存的时间:
@model DateTime?
<div>
<h2>Actions</h2>
<ul>
<li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
<li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAsynchronous">CacheGetOrCreateAsynchronous</a></li>
<li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAbs">CacheGetOrCreateAbs</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAbsSliding">CacheGetOrCreateAbsSliding</a></li>
</ul>
</div>
<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>
如果在超时期限内存在请求,则缓存 DateTime
值将保留在缓存中。
以下代码使用GetOrCreate和GetOrCreateAsync来缓存数据。
public IActionResult CacheGetOrCreate()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return DateTime.Now;
});
return View("Cache", cacheEntry);
}
public async Task<IActionResult> CacheGetOrCreateAsynchronous()
{
var cacheEntry = await
_cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
return View("Cache", cacheEntry);
}
以下代码调用Get 获取缓存时间:
public IActionResult CacheGet()
{
var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
return View("Cache", cacheEntry);
}
下面的代码获取或创建具有绝对过期的缓存项:
public IActionResult CacheGetOrCreateAbs()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
return DateTime.Now;
});
return View("Cache", cacheEntry);
}
只有具有可调过期的缓存项集存在过时的风险。如果访问的时间比滑动过期时间间隔更频繁,则该项将永不过期。将弹性过期与绝对过期组合在一起,以保证项目在其绝对过期时间通过后过期。绝对过期会将项的上限设置为可缓存项的时间,同时仍允许项在可调整过期时间间隔内未请求时提前过期。如果同时指定了绝对过期和可调过期时间,则过期时间以逻辑方式运算。如果滑动过期时间间隔或绝对过期时间通过,则从缓存中逐出该项。
下面的代码获取或创建具有可调和绝对过期的缓存项:
public IActionResult CacheGetOrCreateAbsSliding()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.SetSlidingExpiration(TimeSpan.FromSeconds(3));
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
return DateTime.Now;
});
return View("Cache", cacheEntry);
}
前面的代码保证数据的缓存时间不超过绝对时间。
GetOrCreate、GetOrCreateAsync和 Get 是 CacheExtensions 类中的扩展方法。这些方法扩展了 IMemoryCache的功能。
MemoryCacheEntryOptionsMemoryCacheEntryOptions
下面的示例执行以下操作:
- 设置可调过期时间。访问此缓存项的请求将重置可调过期时钟。
- 将缓存优先级设置为CacheItemPriority. NeverRemove。
- 设置在从缓存中逐出项后将调用的PostEvictionDelegate 。在代码中运行该回调的线程不同于从缓存中移除条目的线程。
public IActionResult CreateCallbackEntry()
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Pin to cache.
.SetPriority(CacheItemPriority.NeverRemove)
// Add eviction callback
.RegisterPostEvictionCallback(callback: EvictionCallback, state: this);
_cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);
return RedirectToAction("GetCallbackEntry");
}
public IActionResult GetCallbackEntry()
{
return View("Callback", new CallbackViewModel
{
CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
Message = _cache.Get<string>(CacheKeys.CallbackMessage)
});
}
public IActionResult RemoveCallbackEntry()
{
_cache.Remove(CacheKeys.CallbackEntry);
return RedirectToAction("GetCallbackEntry");
}
private static void EvictionCallback(object key, object value,
EvictionReason reason, object state)
{
var message = $"Entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}
使用 SetSize、Size 和 SizeLimit 限制缓存大小Use SetSize, Size, and SizeLimit to limit cache size
MemoryCache
实例可以选择指定并强制实施大小限制。缓存大小限制没有定义的度量单位,因为缓存没有度量条目大小的机制。如果设置了缓存大小限制,则所有条目都必须指定 size。ASP.NET Core 运行时不会根据内存压力限制缓存大小。开发人员需要限制缓存大小。指定的大小以开发人员选择的单位为单位。
例如:
- 如果 web 应用主要是缓存字符串,则每个缓存条目大小都可以是字符串长度。
- 应用可以将所有条目的大小指定为1,而大小限制则为条目的计数。
如果未设置 SizeLimit,则缓存将不受限制。当系统内存不足时,ASP.NET Core 运行时不会剪裁缓存。应用必须构建为:
下面的代码创建一个无单位固定大小 MemoryCache 可通过依赖关系注入进行访问:
// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache
{
public MemoryCache Cache { get; set; }
public MyMemoryCache()
{
Cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 1024
});
}
}
SizeLimit
没有单位。如果已设置缓存大小限制,则缓存条目必须以其认为最适合的任何单位指定大小。缓存实例的所有用户都应使用同一单元系统。如果缓存条目大小的总和超出 SizeLimit
指定的值,则不会缓存条目。如果未设置任何缓存大小限制,则将忽略在该项上设置的缓存大小。
下面的代码向依赖关系注入容器注册 MyMemoryCache
。
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSingleton<MyMemoryCache>();
}
对于识别此大小限制缓存并知道如何适当设置缓存条目大小的组件,MyMemoryCache
创建为独立的内存缓存。
以下代码使用 MyMemoryCache
:
public class SetSize : PageModel
{
private MemoryCache _cache;
public static readonly string MyKey = "_MyKey";
public SetSize(MyMemoryCache memoryCache)
{
_cache = memoryCache.Cache;
}
[TempData]
public string DateTime_Now { get; set; }
public IActionResult OnGet()
{
if (!_cache.TryGetValue(MyKey, out string cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now.TimeOfDay.ToString();
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Set cache entry size by extension method.
.SetSize(1)
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Set cache entry size via property.
// cacheEntryOptions.Size = 1;
// Save data in cache.
_cache.Set(MyKey, cacheEntry, cacheEntryOptions);
}
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
}
缓存项的大小可以 Size 或 SetSize 扩展方法进行设置:
public IActionResult OnGet()
{
if (!_cache.TryGetValue(MyKey, out string cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now.TimeOfDay.ToString();
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Set cache entry size by extension method.
.SetSize(1)
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Set cache entry size via property.
// cacheEntryOptions.Size = 1;
// Save data in cache.
_cache.Set(MyKey, cacheEntry, cacheEntryOptions);
}
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
MemoryCacheMemoryCache.Compact
MemoryCache.Compact
尝试按以下顺序删除缓存的指定百分比:
- 所有过期项。
- 按优先级排序。首先删除最低优先级项。
- 最近最少使用的对象。
- 绝对过期的项。
- 具有最早的可调过期项的项。
永远不会删除优先级为 NeverRemove 的固定项。以下代码将删除缓存项并调用 Compact
:
_cache.Remove(MyKey);
// Remove 33% of cached items.
_cache.Compact(.33);
cache_size = _cache.Count;
有关详细信息,请参阅GitHub 上的 Compact 源。
缓存依赖关系Cache dependencies
以下示例演示在依赖项过期时如何使缓存项过期。将 CancellationChangeToken 添加到缓存的项。如果对 CancellationTokenSource
调用 Cancel
,则会逐出两个缓存条目。
public IActionResult CreateDependentEntries()
{
var cts = new CancellationTokenSource();
_cache.Set(CacheKeys.DependentCTS, cts);
using (var entry = _cache.CreateEntry(CacheKeys.Parent))
{
// expire this entry if the dependant entry expires.
entry.Value = DateTime.Now;
entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);
_cache.Set(CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cts.Token));
}
return RedirectToAction("GetDependentEntries");
}
public IActionResult GetDependentEntries()
{
return View("Dependent", new DependentViewModel
{
ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
Message = _cache.Get<string>(CacheKeys.DependentMessage)
});
}
public IActionResult RemoveChildEntry()
{
_cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
return RedirectToAction("GetDependentEntries");
}
private static void DependentEvictionCallback(object key, object value,
EvictionReason reason, object state)
{
var message = $"Parent entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}
使用 CancellationTokenSource 允许以组的形式逐出多个缓存条目。在上面的代码中,在 using
模式下,在 using
块中创建的缓存条目将继承触发器和过期设置。
附加说明Additional notes
- 不会在后台进行过期。没有计时器可主动扫描过期项目的缓存。缓存中的任何活动(
Get
、Set
、Remove
)都可以触发过期项的后台扫描。CancellationTokenSource
(CancelAfter)上的计时器还会删除项,并触发扫描过期项。下面的示例使用CancellationTokenSource (TimeSpan)作为已注册令牌。此令牌激发后,会立即删除该条目,并激发逐出回调:
public IActionResult CacheAutoExpiringTryGetValueSet()
{
DateTime cacheEntry;
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
cacheEntry = DateTime.Now;
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var cacheEntryOptions = new MemoryCacheEntryOptions()
.AddExpirationToken(new CancellationChangeToken(cts.Token));
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
使用回调重新填充缓存项时:
- 多个请求可能会发现缓存的键值为空,因为回调尚未完成。
- 这可能会导致多个线程重新填充缓存的项。
使用一个缓存条目创建另一个缓存条目时,子条目会复制父条目的过期令牌以及基于时间的过期设置。手动删除或更新父项时,子级不会过期。
使用 PostEvictionCallbacks 设置从缓存中逐出缓存项后将触发的回调。
对于大多数应用,
IMemoryCache
已启用。例如,在Add{Service}
中调用AddMvc
、AddControllersWithViews
、AddRazorPages
、AddMvcCore().AddRazorViewEngine
以及许多其他ConfigureServices
方法会启用IMemoryCache
。对于未调用上述某个Add{Service}
方法的应用,可能需要在ConfigureServices
调用 AddMemoryCache。
其他资源Additional resources
- ASP.NET Core 中的分布式缓存
- 使用 ASP.NET Core 中的更改令牌检测更改
- 响应缓存在 ASP.NET Core
- 响应缓存在 ASP.NET Core 中的中间件
- ASP.NET Core MVC 中的缓存标记帮助程序
- ASP.NET Core 中的分布式缓存标记帮助程序
作者: Rick Anderson、 John Luo和Steve Smith
缓存基础知识Caching basics
通过减少生成内容所需的工作,缓存可以显著提高应用的性能和可伸缩性。缓存对不经常更改的数据效果最佳。缓存生成的数据副本的返回速度可以比从原始源返回更快。应编写并测试代码,使其绝不会依赖于缓存的数据。
ASP.NET Core 支持多种不同的缓存。最简单的缓存基于IMemoryCache,这表示存储在 web 服务器内存中的缓存。在服务器场中运行的应用(多台服务器)应确保会话在使用内存中缓存时处于粘滞。粘滞会话确保以后来自客户端的请求都发送到相同的服务器。例如,Azure Web 应用使用应用程序请求路由(ARR)将来自用户代理的所有请求路由到同一服务器。
Web 场中的非粘滞会话需要分布式缓存,以避免缓存一致性问题。对于某些应用,分布式缓存可支持比内存中缓存更高的向外扩展。使用分布式缓存可将缓存内存卸载到外部进程。
内存中缓存可以存储任何对象。分布式缓存接口仅限 byte[]
。内存中和分布式缓存将缓存项作为键值对。
System.Runtime.Caching/MemoryCacheSystem.Runtime.Caching/MemoryCache
System.Runtime.Caching/MemoryCache (NuGet 包)可用于:
- .NET Standard 2.0 或更高版本。
- 面向 .NET Standard 2.0 或更高版本的任何.net 实现。例如,ASP.NET Core 2.0 或更高版本。
- .NET Framework 4.5 或更高版本。
/System.Runtime.Caching
建议将/IMemoryCache
(本文所述)的(本文中介绍),因为它更好地集成到 MemoryCache
中。例如,IMemoryCache
与 ASP.NET Core依赖关系注入一起使用。
将 ASP.NET 4.x 中的代码移植到 ASP.NET Core 时,使用 System.Runtime.Caching
/MemoryCache
作为兼容性桥。
缓存指南Cache guidelines
- 代码应始终具有回退选项,以获取数据,而不是依赖于可用的缓存值。
- 缓存使用稀有资源内存。限制缓存增长:
- 不要使用外部输入作为缓存键。
- 使用过期限制缓存增长。
- 使用 SetSize、Size 和 SizeLimit 限制缓存大小。ASP.NET Core 运行时不会根据内存压力限制缓存大小。开发人员需要限制缓存大小。
使用 IMemoryCacheUsing IMemoryCache
警告
使用依赖关系注入中的共享内存缓存并调用 SetSize
、Size
或 SizeLimit
来限制缓存大小可能会导致应用程序失败。在缓存上设置大小限制时,在添加时,所有项都必须指定大小。这可能会导致问题,因为开发人员可能无法完全控制使用共享缓存的内容。例如,Entity Framework Core 使用共享缓存并且未指定大小。如果应用设置了缓存大小限制并使用 EF Core,则应用将引发 InvalidOperationException
。使用 SetSize
、Size
或 SizeLimit
限制缓存时,为缓存创建一个缓存单独。有关详细信息和示例,请参阅使用 SetSize、Size 和 SizeLimit 限制缓存大小。
内存中缓存是从应用程序中使用依赖关系注入引用的一种服务。ConfigureServices
调用 AddMemoryCache
:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
请求构造函数中的 IMemoryCache
实例:
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
IMemoryCache
需要 NuGet 包 AspNetCore,此包可在元包中找到的。
以下代码使用TryGetValue来检查缓存中是否有时间。如果未缓存时间,则将创建一个新条目,并将其设置为已添加到缓存中。
public static class CacheKeys
{
public static string Entry { get { return "_Entry"; } }
public static string CallbackEntry { get { return "_Callback"; } }
public static string CallbackMessage { get { return "_CallbackMessage"; } }
public static string Parent { get { return "_Parent"; } }
public static string Child { get { return "_Child"; } }
public static string DependentMessage { get { return "_DependentMessage"; } }
public static string DependentCTS { get { return "_DependentCTS"; } }
public static string Ticks { get { return "_Ticks"; } }
public static string CancelMsg { get { return "_CancelMsg"; } }
public static string CancelTokenSource { get { return "_CancelTokenSource"; } }
}
public IActionResult CacheTryGetValueSet()
{
DateTime cacheEntry;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Save data in cache.
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
}
return View("Cache", cacheEntry);
}
将会显示当前时间和缓存的时间:
@model DateTime?
<div>
<h2>Actions</h2>
<ul>
<li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li>
<li><a asp-controller="Home" asp-action="CacheGet">Get</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li>
<li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li>
<li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
</ul>
</div>
<h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3>
<h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>
如果在超时期限内存在请求,则缓存 DateTime
值将保留在缓存中。下图显示当前时间以及从缓存中检索的较早时间:
以下代码使用GetOrCreate和GetOrCreateAsync来缓存数据。
public IActionResult CacheGetOrCreate()
{
var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return DateTime.Now;
});
return View("Cache", cacheEntry);
}
public async Task<IActionResult> CacheGetOrCreateAsync()
{
var cacheEntry = await
_cache.GetOrCreateAsync(CacheKeys.Entry, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(3);
return Task.FromResult(DateTime.Now);
});
return View("Cache", cacheEntry);
}
以下代码调用Get 获取缓存时间:
public IActionResult CacheGet()
{
var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry);
return View("Cache", cacheEntry);
}
GetOrCreate、GetOrCreateAsync和Get是扩展方法的一部分,扩展方法是扩展 IMemoryCache功能的CacheExtensions类的一部分。有关其他缓存方法的说明,请参阅IMemoryCache 方法和CacheExtensions 方法。
MemoryCacheEntryOptionsMemoryCacheEntryOptions
下面的示例执行以下操作:
- 设置可调过期时间。访问此缓存项的请求将重置可调过期时钟。
- 将缓存优先级设置为
CacheItemPriority.NeverRemove
。 - 设置在从缓存中逐出项后将调用的PostEvictionDelegate 。在代码中运行该回调的线程不同于从缓存中移除条目的线程。
public IActionResult CreateCallbackEntry()
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Pin to cache.
.SetPriority(CacheItemPriority.NeverRemove)
// Add eviction callback
.RegisterPostEvictionCallback(callback: EvictionCallback, state: this);
_cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions);
return RedirectToAction("GetCallbackEntry");
}
public IActionResult GetCallbackEntry()
{
return View("Callback", new CallbackViewModel
{
CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry),
Message = _cache.Get<string>(CacheKeys.CallbackMessage)
});
}
public IActionResult RemoveCallbackEntry()
{
_cache.Remove(CacheKeys.CallbackEntry);
return RedirectToAction("GetCallbackEntry");
}
private static void EvictionCallback(object key, object value,
EvictionReason reason, object state)
{
var message = $"Entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message);
}
使用 SetSize、Size 和 SizeLimit 限制缓存大小Use SetSize, Size, and SizeLimit to limit cache size
MemoryCache
实例可以选择指定并强制实施大小限制。缓存大小限制没有定义的度量单位,因为缓存没有度量条目大小的机制。如果设置了缓存大小限制,则所有条目都必须指定 size。ASP.NET Core 运行时不会根据内存压力限制缓存大小。开发人员需要限制缓存大小。指定的大小以开发人员选择的单位为单位。
例如:
- 如果 web 应用主要是缓存字符串,则每个缓存条目大小都可以是字符串长度。
- 应用可以将所有条目的大小指定为1,而大小限制则为条目的计数。
如果未设置 SizeLimit,则缓存将不受限制。当系统内存不足时,ASP.NET Core 运行时不会剪裁缓存。应用程序的体系结构非常多:
下面的代码创建一个无单位固定大小 MemoryCache 可通过依赖关系注入进行访问:
// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache
{
public MemoryCache Cache { get; set; }
public MyMemoryCache()
{
Cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 1024
});
}
}
SizeLimit
没有单位。如果已设置缓存大小限制,则缓存条目必须以其认为最适合的任何单位指定大小。缓存实例的所有用户都应使用同一单元系统。如果缓存条目大小的总和超出 SizeLimit
指定的值,则不会缓存条目。如果未设置任何缓存大小限制,则将忽略在该项上设置的缓存大小。
下面的代码向依赖关系注入容器注册 MyMemoryCache
。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<MyMemoryCache>();
}
对于识别此大小限制缓存并知道如何适当设置缓存条目大小的组件,MyMemoryCache
创建为独立的内存缓存。
以下代码使用 MyMemoryCache
:
public class AboutModel : PageModel
{
private MemoryCache _cache;
public static readonly string MyKey = "_MyKey";
public AboutModel(MyMemoryCache memoryCache)
{
_cache = memoryCache.Cache;
}
[TempData]
public string DateTime_Now { get; set; }
public IActionResult OnGet()
{
if (!_cache.TryGetValue(MyKey, out string cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now.TimeOfDay.ToString();
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Set cache entry size by extension method.
.SetSize(1)
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Set cache entry size via property.
// cacheEntryOptions.Size = 1;
// Save data in cache.
_cache.Set(MyKey, cacheEntry, cacheEntryOptions);
}
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
}
public IActionResult OnGet()
{
if (!_cache.TryGetValue(MyKey, out string cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = DateTime.Now.TimeOfDay.ToString();
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Set cache entry size by extension method.
.SetSize(1)
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Set cache entry size via property.
// cacheEntryOptions.Size = 1;
// Save data in cache.
_cache.Set(MyKey, cacheEntry, cacheEntryOptions);
}
DateTime_Now = cacheEntry;
return RedirectToPage("./Index");
}
MemoryCacheMemoryCache.Compact
MemoryCache.Compact
尝试按以下顺序删除缓存的指定百分比:
- 所有过期项。
- 按优先级排序。首先删除最低优先级项。
- 最近最少使用的对象。
- 绝对过期的项。
- 具有最早的可调过期项的项。
永远不会删除优先级为 NeverRemove 的固定项。
_cache.Remove(MyKey);
// Remove 33% of cached items.
_cache.Compact(.33);
cache_size = _cache.Count;
有关详细信息,请参阅GitHub 上的 Compact 源。
缓存依赖关系Cache dependencies
以下示例演示在依赖项过期时如何使缓存项过期。将 CancellationChangeToken 添加到缓存的项。如果对 CancellationTokenSource
调用 Cancel
,则会逐出两个缓存条目。
public IActionResult CreateDependentEntries()
{
var cts = new CancellationTokenSource();
_cache.Set(CacheKeys.DependentCTS, cts);
using (var entry = _cache.CreateEntry(CacheKeys.Parent))
{
// expire this entry if the dependant entry expires.
entry.Value = DateTime.Now;
entry.RegisterPostEvictionCallback(DependentEvictionCallback, this);
_cache.Set(CacheKeys.Child,
DateTime.Now,
new CancellationChangeToken(cts.Token));
}
return RedirectToAction("GetDependentEntries");
}
public IActionResult GetDependentEntries()
{
return View("Dependent", new DependentViewModel
{
ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent),
ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child),
Message = _cache.Get<string>(CacheKeys.DependentMessage)
});
}
public IActionResult RemoveChildEntry()
{
_cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel();
return RedirectToAction("GetDependentEntries");
}
private static void DependentEvictionCallback(object key, object value,
EvictionReason reason, object state)
{
var message = $"Parent entry was evicted. Reason: {reason}.";
((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message);
}
使用 CancellationTokenSource
允许以组的形式逐出多个缓存条目。在上面的代码中,在 using
模式下,在 using
块中创建的缓存条目将继承触发器和过期设置。
附加说明Additional notes
使用回调重新填充缓存项时:
- 多个请求可能会发现缓存的键值为空,因为回调尚未完成。
- 这可能会导致多个线程重新填充缓存的项。
使用一个缓存条目创建另一个缓存条目时,子条目会复制父条目的过期令牌以及基于时间的过期设置。手动删除或更新父项时,子级不会过期。
使用PostEvictionCallbacks设置从缓存中逐出缓存项后将触发的回调。