【问题标题】:cache TryGetValue versus Get缓存 TryGetValue 与 Get
【发布时间】:2020-06-03 05:15:34
【问题描述】:

这就是我实现 CacheManager 的方式。我面临的问题是 TryGetValue 将始终在 RemoveFromCache 函数中返回 null。在其中一个令牌过期后调用此函数,因此我试图从缓存中的列表中清除该令牌,而 GetAllTokens 正在返回所有令牌的完整列表。 AddTokenToCache 工作正常。

它是 ASPNET-Core 3.0 上的 WebAPI

CacheManager.cs

public class CacheManager : ICacheManager
{
    private IMemoryCache _cache;

    public CacheManager(IMemoryCache cache) {
        _cache = cache;

    }        
    public void AddTokenToCache(string appName, string tokenString)
    {
        List<Token> tokens = new List<Token>();

        //save this token against the application record in-memory
        if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens))
        {
            if (tokens == null)
                tokens = new List<Token>();

        }

        tokens.Add(new Token
        {
            AppName = appName,
            GeneratedAt = DateTime.Now,
            TokenId = tokenString
        });

        // Set cache options.
        var cacheEntryOptions = new MemoryCacheEntryOptions()
        ;//    .SetSlidingExpiration(TimeSpan.FromSeconds(180)); //3 minutes

        _cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);
    }

    public List<Token> GetAllTokens()
    {
        return _cache.Get<List<Token>>(CacheHelper.CacheKey_Tokens);
    }

    public bool RemoveFromCache(string tokenId)
    {

        List<Token> tokens = new List<Token>();            

        //remove this token from memory            
        if (!_cache.TryGetValue(CacheHelper.CacheKey_Tokens, out tokens)) {
            return false;
        }
        else
        {
            if (tokens != null && tokens.Count > 0)
            {
                //_logger.LogInfo("Processing token");
                //trimming quotations from the string
                tokenId = tokenId.Substring(1, tokenId.Length - 2);
                int index = tokens.FindIndex(t => t.TokenId == tokenId);

                if (index >= 0)
                    tokens.RemoveAt(index);

                var cacheEntryOptions = new MemoryCacheEntryOptions();
                _cache.Set(CacheHelper.CacheKey_Tokens, tokens, cacheEntryOptions);

                return true;
            }                
        }

        return false;
    }
}

我的调用顺序是:

  • AddTokenToCache(令牌成功添加到缓存)
  • GetAllToken(显示一个令牌被添加到缓存中)
  • AddTokenToCache(令牌成功添加到缓存)
  • GetAllToken(显示两个令牌都已添加到缓存中)
  • 触发调用 RemoveFromCache 的 TokenExpired 事件(令牌为空)
  • GetAllToken(显示两个令牌都已添加到缓存中)

Startup.cs

public void ConfigureServices(IServiceCollection services)
    { 
        services.AddSingleton<ILoggerManager, LoggerManager>();

        services.AddMemoryCache();
        services.AddDbContext<GEContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddControllers();
        services.AddRazorPages();
        services.AddSingleton<ICacheManager, CacheManager>();

        RegisterHandlerforTokenExpiredEvent(services);
        //other code removed for brevity
    }
public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services)
    {
        var sp = services.BuildServiceProvider();

        var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher            
        var cacheManager = sp.GetService<ICacheManager>(); //subscriber

        jwtManager.TokenExpired += cacheManager.OnTokenExpired;            
    }

【问题讨论】:

  • 1.我尝试了您的代码但无法重现,您是否正确注入了服务?您能否包括启动和调用 CacheManager 的代码? 2.有没有重现的demo?
  • 确定..我正在添加 Startup.cs
  • 是的,我在删除时从另一个点调用缓存服务......让我分享代码 - 是这个原因......那么替代方案是什么?

标签: asp.net-core caching memcached


【解决方案1】:

那是因为您通过services.BuildServiceProvider() 构建了另一个ServiceProvider

public void RegisterHandlerforTokenExpiredEvent(IServiceCollection services)

{
    var sp = services.BuildServiceProvider();  // this is a different service provider from the default one built by ASP.NET Core itself.

    var jwtManager = sp.GetService<IJWTAuthenticationManager>(); //publisher            
    var cacheManager = sp.GetService<ICacheManager>(); //subscriber
    // it doesn't work because the cacheManager is not the same instance that you use in the controllers
    jwtManager.TokenExpired += cacheManager.OnTokenExpired;            
}

因此,您获得的ICacheManager 实例与您在控制器/其他服务中注入的不是同一个单例。换句话说,您将拥有两个不同的 ICacheManager 实例!

作为黄金法则,请勿在您的应用层代码中通过services.BuildServiceProvider() 构建另一个 ServiceProvider 副本,除非您确定它适合您。

如何解决

  1. 您应该始终使用 IoC 而不是服务定位器模式,而不是构建服务提供者的另一个副本然后获取另一个实例。
  2. 您的JWTAuthenticationManager 似乎是一个单例,您想在启动时绑定事件处理程序。如果是这种情况,您可以注册一个 HostedService。

    public class MyHostedService : IHostedService
    {
        private readonly IJWTAuthenticationManager _jWTAuthManager;
        private readonly ICacheManager _cacheManager;
    
        // suppose your IJWTAuthenticationManager  is a singleton service
        public MyHostedService(IJWTAuthenticationManager jWTAuthManager, ICacheManager cacheManager)
        {
            this._jWTAuthManager = jWTAuthManager;
            this._cacheManager = cacheManager;
        }
    
        public Task StartAsync(CancellationToken cancellationToken)
        {
            this._jWTAuthManager.TokenExpired += this._cacheManager.OnTokenExpired;
            return Task.CompletedTask;
        }
    
        public Task StopAsync(CancellationToken cancellationToken)
        {
            this._jWTAuthManager.TokenExpired -= this._cacheManager.OnTokenExpired;
            return Task.CompletedTask;
        }
    }
    

    并在 Startup 中注册此服务:

    services.AddHostedService<MyHostedService>();
    

另一种不需要HostedService,在启动时启动的方式:

获取服务并绑定Host.Run()之前的事件:

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    var jwtMgr = host.Services.GetRequiredService<IJWTAuthenticationManager>();
    var cacheMgr = host.Services.GetRequiredService<ICacheManager>();
    jwtMgr.TokenExpired = cacheMgr.OnTokenExpired;
    host.Run();
}

【讨论】:

  • OHhh...好的...这是一个更好的方法...谢谢!!
  • 只是想知道 services.AddScoped 可以吗?而不是 AddHostedService 我对启动和停止服务有点不舒服,我只想要一个具有应用程序生命周期(Apppool)的 MyHostedService 单例实例(如果我没有意义,请道歉)...... cachemanager 和 JWTAuthenticationManager 都是单例的。
  • @Samra 在 HostedService 中使用 ScopedService 需要稍作改动。如果您不喜欢 HostedService,您可以在主机运行之前手动获取服务并手动绑定事件。有关详细信息,请参阅我的更新答案:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-28
  • 2023-03-15
  • 1970-01-01
  • 2012-08-15
  • 2012-02-27
  • 1970-01-01
相关资源
最近更新 更多