c# - 是否有可移植类库的内存缓存?

标签 c# caching windows-phone-8 portable-class-library

我正在构建一个需要某种缓存的应用程序,并且由于我的模型和 View 模型存在于可移植类库中,所以我也希望在那里进行某种缓存...但是 PCL 是有限的,而且我似乎找不到任何。

我想使用 System.Runtime.Caching,但 PCL 中没有包含它 - 还有其他选择吗?

最佳答案

我自己也在寻找这样的选择;我将很快推出我自己的实现,并将发布它。在此之前,您可能希望尝试以下一些想法。

Runtime.Cache 如果我没记错的话可以根据绝对时间、滑动时间和监控键/文件使缓存失效。

首先弄清楚你需要实现哪一个(我需要绝对和滑动虽然我相信滑动会更昂贵)

看看这个链接。 alternative-to-concurrentdictionary-for-portable-class-library

我的想法是,为了在每个缓存项添加/删除时扩展该类,我们将缓存键存储/删除到字典对象中并设置到期时间(对于滑动项,我们将在读取时更新此到期时间)

然后我们有一个主计时器,它每隔一段时间检查这个字典并根据时间到期。

如果您还想要,您可以使用另一个字典来跟踪键依赖关系,因此在键过期时您可以根据此过期任何其他缓存。

希望这能以某种方式有所帮助。

---更新

我设法开始编写自己的实现...给你......

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;

namespace RanaInside.CacheProvider
{
internal class CacheProvider : ICacheProvider
{
    private IDictionary<object, CacheItem> _cache = new Dictionary<object, CacheItem>();
    private IDictionary<object, SliderDetails> _slidingTime = new Dictionary<object, SliderDetails>();

    private const int _monitorWaitDefault = 1000;
    private const int _monitorWaitToUpdateSliding = 500;

    /// <summary>
    /// The sync object to make accessing the dictionary is thread safe.
    /// </summary>
    private readonly object _sync = new object();

    #region Implementation of Interface

    public event EventHandler KeyRemoved;

    /// <summary>
    /// Add a value to the cache with a relative expiry time, e.g 10 minutes.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <param name="key">The key.</param>
    /// <param name="value">The value.</param>
    /// <param name="slidingExpiry">The sliding time when the key value pair should expire and be purged from the cache.</param>
    /// <param name="priority">Normal priority will be purged on low memory warning.</param>
    public void Add<TKey, TValue>(TKey key, TValue value, TimeSpan slidingExpiry, CacheItemPriority priority = CacheItemPriority.Normal) where TValue : class
    {
        Add(key, value, slidingExpiry, priority, true);
    }

    /// <summary>
    /// Add a value to the cache with an absolute time, e.g. 01/01/2020.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <param name="key">The key.</param>
    /// <param name="value">The value.</param>
    /// <param name="absoluteExpiry">The absolute date time when the cache should expire and be purged the value.</param>
    /// <param name="priority">Normal priority will be purged on low memory warning.</param>
    public void Add<TKey, TValue>(TKey key, TValue value, DateTime absoluteExpiry, CacheItemPriority priority = CacheItemPriority.Normal) where TValue : class
    {
        if (absoluteExpiry < DateTime.Now)
        {
            return;
        }

        var diff = absoluteExpiry - DateTime.Now;
        Add(key, value, diff, priority, false);
    }

    /// <summary>
    /// Gets a value from the cache for specified key.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <param name="key">The key.</param>
    /// <returns>
    /// If the key exists in the cache then the value is returned, if the key does not exist then null is returned.
    /// </returns>
    public TValue Get<TKey, TValue>(TKey key) where TValue : class
    {
        try
        {
            var cacheItem = _cache[key];

            if (cacheItem.RelativeExpiry.HasValue)
            {
                if (Monitor.TryEnter(_sync, _monitorWaitToUpdateSliding))
                {
                    try
                    {
                        _slidingTime[key].Viewed();
                    }
                    finally
                    {
                        Monitor.Exit(_sync);
                    }
                }
            }

            return (TValue)cacheItem.Value;
        }
        catch (Exception)
        {
            return null;
        }
    }

    /// <summary>
    /// Remove a value from the cache for specified key.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <param name="key">The key.</param>
    public void Remove<TKey>(TKey key)
    {
        if (!Equals(key, null))
        {
            _cache.Remove(key);
            _slidingTime.Remove(key);

            if (KeyRemoved != null)
            {
                KeyRemoved(key, new EventArgs());
            }
        }
    }

    /// <summary>
    /// Clears the contents of the cache.
    /// </summary>
    public void Clear()
    {
        if (!Monitor.TryEnter(_sync, _monitorWaitDefault))
        {
            return;
        }

        try
        {
            _cache.Clear();
            _slidingTime.Clear();
        }
        finally
        {
            Monitor.Exit(_sync);
        }
    }

    /// <summary>
    /// Gets an enumerator for keys of a specific type.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <returns>
    /// Returns an enumerator for keys of a specific type.
    /// </returns>
    public IEnumerable<TKey> Keys<TKey>()
    {
        if (!Monitor.TryEnter(_sync, _monitorWaitDefault))
        {
            return Enumerable.Empty<TKey>();
        }

        try
        {
            return _cache.Keys.Where(k => k.GetType() == typeof(TKey)).Cast<TKey>().ToList();
        }
        finally
        {
            Monitor.Exit(_sync);
        }
    }

    /// <summary>
    /// Gets an enumerator for all the keys
    /// </summary>
    /// <returns>
    /// Returns an enumerator for all the keys.
    /// </returns>
    public IEnumerable<object> Keys()
    {
        if (!Monitor.TryEnter(_sync, _monitorWaitDefault))
        {
            return Enumerable.Empty<object>();
        }

        try
        {
            return _cache.Keys.ToList();
        }
        finally
        {
            Monitor.Exit(_sync);
        }
    }

    /// <summary>
    /// Gets the total count of items in cache
    /// </summary>
    /// <returns>
    /// -1 if failed
    /// </returns>
    public int TotalItems()
    {
        if (!Monitor.TryEnter(_sync, _monitorWaitDefault))
        {
            return -1;
        }

        try
        {
            return _cache.Keys.Count;
        }
        finally
        {
            Monitor.Exit(_sync);
        }
    }

    /// <summary>
    /// Purges all cache item with normal priorities.
    /// </summary>
    /// <returns>
    /// Number of items removed (-1 if failed)
    /// </returns>
    public int PurgeNormalPriorities()
    {
        if (Monitor.TryEnter(_sync, _monitorWaitDefault))
        {
            try
            {
                var keysToRemove = (from cacheItem in _cache where cacheItem.Value.Priority == CacheItemPriority.Normal select cacheItem.Key).ToList();

                return keysToRemove.Count(key => _cache.Remove(key));
            }
            finally
            {
                Monitor.Exit(_sync);
            }
        }

        return -1;
    }

    #endregion


    #region Private class helper

    private void Add<TKey, TValue>(TKey key, TValue value, TimeSpan timeSpan, CacheItemPriority priority, bool isSliding) where TValue : class
    {
        if (!Monitor.TryEnter(_sync, _monitorWaitDefault))
        {
            return;
        }

        try
        {
            // add to cache
            _cache.Add(key, new CacheItem(value, priority, ((isSliding) ? timeSpan : (TimeSpan?)null)));

            // keep sliding track
            if (isSliding)
            {
                _slidingTime.Add(key, new SliderDetails(timeSpan));
            }

            StartObserving(key, timeSpan);
        }
        finally
        {
            Monitor.Exit(_sync);
        }
    }

    private void StartObserving<TKey>(TKey key, TimeSpan timeSpan)
    {
        Observable.Timer(timeSpan)
            .Finally(() =>
            {
                // on finished
                GC.Collect();
                GC.WaitForPendingFinalizers();
            })
            // on next
            .Subscribe(x => TryPurgeItem(key), 
            exception =>
            {
                // on error: Purge Failed with exception.Message
            });
    }

    private void TryPurgeItem<TKey>(TKey key)
    {
        if (_slidingTime.ContainsKey(key))
        {
            TimeSpan tryAfter;
            if (!_slidingTime[key].CanExpire(out tryAfter))
            {
                // restart observing
                StartObserving(key, tryAfter);
                return;
            }
        }

        Remove(key);
    }

    private class CacheItem
    {
        public CacheItem() { }

        public CacheItem(object value, CacheItemPriority priority, TimeSpan? relativeExpiry = null)
        {
            Value = value;
            Priority = priority;
            RelativeExpiry = relativeExpiry;
        }

        public object Value { get; set; }

        public CacheItemPriority Priority { get; set; }

        public TimeSpan? RelativeExpiry { get; set; }
    }


    private class SliderDetails
    {
        public SliderDetails(TimeSpan relativeExpiry)
        {
            RelativeExpiry = relativeExpiry;
            Viewed();
        }

        private TimeSpan RelativeExpiry { get; set; }

        private DateTime ExpireAt { get; set; }

        public bool CanExpire(out TimeSpan tryAfter)
        {
            tryAfter = (ExpireAt - DateTime.Now);
            return (0 > tryAfter.Ticks);
        }

        public void Viewed()
        {
            ExpireAt = DateTime.Now.Add(RelativeExpiry);
            var z = ExpireAt;
        }
    }

    #endregion

}
}

有关最新更新,请访问我的博客

http://ranahossain.blogspot.co.uk/2014/01/cache-provider-for-portable-class.html

关于c# - 是否有可移植类库的内存缓存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20160014/

相关文章:

c# - 如何在 STAThread 模式下使用 resharper runner 运行 mspec

c# - RDLC 报告超链接在浏览器中不起作用

c# - 为什么字符串相等比较字符?

php - 在 Laravel 中使用用户过滤器缓存数据(不仅如此)

javascript - 缓存文档对象对性能有什么好处吗?

c# - 如何在调用 WindowsPhoneRuntimeComponent 时使用字节数组作为参数?

c# - 使用 DirectX Texture2D 显示相机预览会导致 Windows Phone 8 上出现振荡

c# - 如何将 ulong 转换为正 int?

java - Spring Memcached 注解不缓存

c# - 在 Windows Phone 中将字符串拆分为多个文本框