.net core InMemoryCache выдает ошибку

#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;
        }
  

Вы могли бы каждый раз предоставлять новый контекст, но без какой-либо синхронизации вы, вероятно, сделали бы несколько запросов для одного и того же значения.