首页 > 解决方案 > .net core InMemoryCache 抛出错误

问题描述

我正在使用 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()

但是我们将 DB 上下文注入到构造函数中。粘贴我在这里使用的以下代码片段。

 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 错误。注意:如果我重新部署相同的代码,问题会在某一天得到解决。但是错误一次又一次地出现。

标签: c#asp.net-corecachingin-memory

解决方案


您在此处收到此行的错误:

 softwareComponents = await Task.Run(() => _ctx.xyztable.AsNoTracking()
                    .Include(a => a.SoftwareComponentEnvironments).AsNoTracking());

Task.Run> 将指定的工作排入队列以在 ThreadPool 上运行并返回该工作的任务或任务句柄。

这意味着那里的委托将在单独的线程上运行。

因此,如果您请求其他一些缓存数据(甚至是相同的,在第一个完成之前),那么另一个线程将启动并使用相同的上下文。这在 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;
        }

您可以每次都提供一个新的上下文,但如果没有某种同步,您可能会针对同一个值进行多次查询。


推荐阅读