#c# #asp.net-core #caching #in-memory
#c# #asp.net-core #кэширование #в памяти
Вопрос:
Я использую InMemoryCache, как показано ниже. Я не настраивал через startup.cs. Проблема здесь в том, что он выдает следующую ошибку в размещенном образе docker, не сталкиваясь с этой ошибкой локально.
[Error] ConnectionId:0 RequestPath:/BuildSpec/RetrieveFor RequestId:1:0, SpanId:|aaaaa-ccccccc., a-415ea81b37b05aaa4f3, ParentId: Controller.RetrieveFor (core.web) => Microsoft.EntityFrameworkCore.Query: An exception occurred while iterating over the results of a query for context type 'act.core.data.ActDbContext'.
System.InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
Но мы вводим контекст БД в конструктор. Вставляю следующий фрагмент кода, который я использую здесь.
private static readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions
{
SizeLimit = 10
});
private async Task<IQueryable<SoftwareComponent>> GetOrCreateSoftwareComponent()
{
const string key = "xyz";
if (!_cache.TryGetValue(key, out IQueryable<xyz> xyz))
{
softwareComponents = await Task.Run(() => _ctx.xyztable.AsNoTracking()
.Include(a => a.SoftwareComponentEnvironments).AsNoTracking());
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetSize(1)
.SetSlidingExpiration(TimeSpan.FromMinutes(30))
.SetAbsoluteExpiration(TimeSpan.FromMinutes(60));
_cache.Set(key, xyz, cacheEntryOptions);
}
return softwareComponents;
}
Проблема здесь в том, что я не получаю ошибку здесь. Почему это ошибка MySQL.
Примечание: если я повторно разверну тот же код, проблема когда-нибудь разрешится. Но ошибка возвращается снова и снова.
Комментарии:
1. Не могли бы вы поделиться кодом внедрения, пожалуйста?
2. частный ActDbContext _ctx только для чтения; общедоступный BuildSpecificationFactory(ActDbContext ctx) { _ctx = ctx; }
Ответ №1:
Вы получаете ошибку в этой строке здесь:
softwareComponents = await Task.Run(() => _ctx.xyztable.AsNoTracking()
.Include(a => a.SoftwareComponentEnvironments).AsNoTracking());
Task.Run
> Ставит указанную работу в очередь для выполнения в пуле потоков и возвращает задачу или дескриптор задачи для этой работы.
Это означает, что делегат, находящийся там, будет выполняться в отдельном потоке.
Поэтому, если вы запросите некоторые другие данные кэша (или даже те же самые, до того, как будет выполнен первый), тогда запустится еще один поток и будет использовать тот же контекст. Это не разрешено в EF, поэтому вы получаете ошибку.
Простой обходной путь — использовать блокировку
private readonly object lockObject = new object();
private async Task<IQueryable<SoftwareComponent>> GetOrCreateSoftwareComponent()
{
const string key = "xyz";
lock (lockObject)
{
if (!_cache.TryGetValue(key, out IQueryable<xyz> xyz))
{
softwareComponents = await Task.Run(() => _ctx.xyztable.AsNoTracking()
.Include(a => a.SoftwareComponentEnvironments).AsNoTracking());
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetSize(1)
.SetSlidingExpiration(TimeSpan.FromMinutes(30))
.SetAbsoluteExpiration(TimeSpan.FromMinutes(60));
_cache.Set(key, xyz, cacheEntryOptions);
}
}
return softwareComponents;
}
Вы могли бы каждый раз предоставлять новый контекст, но без какой-либо синхронизации вы, вероятно, сделали бы несколько запросов для одного и того же значения.