【问题标题】:Preventing dirty reads when refreshing MemoryCache via CacheEntryRemovedCallback通过 CacheEntryRemovedCallback 刷新 MemoryCache 时防止脏读
【发布时间】:2014-07-08 19:18:09
【问题描述】:

我已经实现了一个 MemoryCache,它通过 CacheEntryRemovedCallback “自动刷新”内容。

但是,当从缓存中删除项目时,我在防止脏读时遇到问题。

我的缓存确实被请求破坏了,我需要确保缓存条目在更新时被阻止读取。

我的代码如下。如您所见,我尝试在更新缓存以及读取数据时使用简单的 lock()。但是,在删除项目并获得锁的时间内完成读取。

public List<Domain.Turbine> IgnorableTurbines
{
    get
    {
        var ignorableTurbines = new List<Domain.Turbine>();
        lock (ignorableTurbineslockObject)
        {
            Log.Info("IgnorableTurbines get lock");
            Log.Info("Getting ignorable turbines");
            ignorableTurbines = (List<Domain.Turbine>)Cache["ignorableturbines"];

        }
        Log.Info("IgnorableTurbines get released");
        return ignorableTurbines;
    }
}

void IgnorableStations_CacheItemRemovedCallback(CacheEntryRemovedArguments arguments)
{

    lock (ignorableTurbineslockObject)
    {
        Log.Info("IgnorableTurbines update locked");
        Log.InfoFormat("Item removed: {1}", arguments.CacheItem.Key);
        var decoupledIgnorableTurbines = _repo.Query<ExcludedStation>().ToList();

        AddIgnorableStations("ignorableturbines", decoupledIgnorableTurbines);
        Log.Info("IgnorableTurbines re-added");
    }
    Log.Info("IgnorableTurbines update released");
}

我的日志是这样说的:

2014-05-20 15:20:45,855 INFO - IgnorableTurbines get lock
2014-05-20 15:20:45,855 INFO - Getting ignorable turbines
2014-05-20 15:20:45,857 INFO - IgnorableTurbines update locked
2014-05-20 15:20:45,863 INFO - Item removed: ignorableturbines
2014-05-20 15:20:45,866 INFO - IgnorableTurbines re-added
2014-05-20 15:20:45,867 INFO - IgnorableTurbines update released
2014-05-20 15:20:45,868 INFO - IgnorableTurbines get released
2014-05-20 15:20:45,871 ERROR - Value cannot be null.
Parameter name: source
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Queryable.AsQueryable[TElement](IEnumerable`1 source)
   at (...).get_IgnorableTurbines()

让我困惑的是,“get”上获得了锁,而在释放之前,“update”上也获得了锁。

(我正在使用 log4net 进行日志记录)

我做错了什么?

【问题讨论】:

  • 提示:使用 PatternLayout 并在 log4net 模式中包含 %thread。
  • 这个序列看起来和我预期的一样。您的 Get 方法尝试从缓存中检索。这会触发 Cache 检查是否过期,并决定删除该项目,在将控制权返回给您的获取代码之前在同一线程上调用 CacheItemRemovedCallback。如果在持有第一个锁的同时第二个锁成功,则您必须在同一个线程上运行。
  • 但是,@Joe,为什么从缓存中获取项目会触发过期检查?过期策略设置为“绝对”,所以过期应该 - 据我所知 - 与有人试图获取该项目时异步?
  • @JesperLundStocholm - 相反,如果您设置了绝对过期,则有一个合约可以使缓存项过期,无论何时检索它都需要检查,与清除线程是否有时间无关跑还是不跑。

标签: c#-4.0 caching thread-safety locking memorycache


【解决方案1】:

昨天我想知道我的问题是否是由在缓存项被删除之后触发的事件引起的,这意味着我试图获得的锁实际上是锁定“无” - 因此导致“值不能为空”。

于是我在 Cache 中挖掘了一下,发现了另一个要订阅的事件,结果发现 CacheItem 对象有一个名为“UpdateCallback”的属性。

所以我将代码改为使用 CachEntryUpdateCallback,现在一切正常。

代码现在用于更新事件:

void IgnorableStations_CacheItemUpdateCallback(CacheEntryUpdateArguments arguments)
{
    Log.Info("Refreshing ignorable stations");
    var decoupledIgnorableTurbines =
    _repo.Query<ExcludedStation>().Select(i => new Domain.Turbine { StationId = i.WtgNumber, WindfarmId = i.WindFarmId }).ToList();

    arguments.UpdatedCacheItem = new CacheItem(arguments.Key, decoupledIgnorableTurbines);
    CacheEntryUpdateCallback ignorableStationsCacheItemBeforeUpdateCallback = IgnorableStations_CacheItemUpdateCallback;
    arguments.UpdatedCacheItemPolicy = new CacheItemPolicy
    {
        AbsoluteExpiration = DateTime.Now.AddMinutes(7.00),
        UpdateCallback = ignorableStationsCacheItemBeforeUpdateCallback
    };
}

以及初始插入:

    void AddIgnorableStations(string key, object ignorableStations)
    {
        CacheEntryUpdateCallback ignorableStationsCacheItemBeforeUpdateCallback = IgnorableStations_CacheItemUpdateCallback;
        var cacheItemPolicy = new CacheItemPolicy
        {
            AbsoluteExpiration = DateTime.Now.AddMinutes(7.00),
            UpdateCallback = ignorableStationsCacheItemBeforeUpdateCallback
        };

        Cache.Set(key, ignorableStations, cacheItemPolicy);
    }

【讨论】:

  • CacheEntryUpdateCallback 是一个很酷但名称错误的委托。它在条目被删除时触发,并允许您“干预”并将其更新为其他内容。此功能可能已添加到 CacheEntryDeletedCallback,而不是创建一个全新的委托。或者至少他们可以将其命名为“CacheEntryUpdateInsteadOfDeleteCallbackDelegate”。
猜你喜欢
  • 2018-05-10
  • 2019-02-25
  • 1970-01-01
  • 1970-01-01
  • 2014-06-06
  • 2012-06-10
  • 2012-03-31
  • 2018-11-27
  • 1970-01-01
相关资源
最近更新 更多