简体   繁体   English

用于频繁读取和罕见写入操作的并发收集(.NET)

[英]Concurrent collection to use for frequent read and rare write operations (.NET)

I want to create a cache in my web application, which will allow the top layer (MVC) to persist some values, retrieved from underlying layers (services & DB), in order to avoid unnecessary requests to it. 我想在我的Web应用程序中创建一个缓存,这将允许顶层(MVC)持久保存从底层(服务和数据库)检索的某些值,以避免对它的不必要的请求。 The data, that I want to store in cache, is needed on every page of the web site, so this cache is aimed to dramatically reduce the amount of requests. 我希望在网站的每个页面上存储我想要存储在缓存中的数据,因此该缓存旨在显着减少请求量。 The idea is that a lot of threads will read the data from collection while one thread will clear it and retrieve new data after cache expiration. 这个想法是很多线程将从集合中读取数据,而一个线程将清除它并在缓存过期后检索新数据。 This means the collection will be used for frequent reading and rare writing operations. 这意味着该集合将用于频繁阅读罕见的书写操作。

The problem for me is to choose the appropriate class for these purposes. 对我来说,问题是为这些目的选择合适的类。 I have read about the set of concurrent classes from System.Collections.Concurrent , introduced in .NET 4. I definitely don't need to use ConcurrentQueue or ConcurrentStack , but I have to choose between BlockingCollection , ConcurrentBag and ConcurrentDictionary . 我已经阅读了.NET 4中引入的System.Collections.Concurrent的并发类集。我绝对不需要使用ConcurrentQueueConcurrentStack ,但我必须在BlockingCollectionConcurrentBagConcurrentDictionary之间进行选择。

ConcurrentBag seems to be the best solution in this case. 在这种情况下, ConcurrentBag似乎是最好的解决方案。 However, I read in this article that concurrent dictionary 但是,我在这篇文章中读到了并发字典

... is entirely lock-free for read operations. ...对于读取操作完全没有锁定。 In this way, it's optimized for scenarios where reading from the dictionary is the most frequent operation. 通过这种方式,它针对从字典中读取最频繁操作的情况进行了优化。

So maybe the best solution is to use ConcurrentDictionary<int, MyTypeOfObj> instead? 那么最好的解决方案是使用ConcurrentDictionary<int, MyTypeOfObj>吗? Or maybe I don't need concurrent type at all and simple List will do the job? 或者我可能根本不需要并发类型,简单的List会完成这项工作吗? Probably, it would do if I can somehow lock operations with it for the time of cache update. 可能,如果我可以以某种方式锁定缓存更新时的操作,它会这样做。 But using simple lock is undesirable. 但是使用简单的lock是不可取的。

Any advices and explanations are appreciated. 任何建议和解释都表示赞赏。

Update 更新

The cache is used to store the map points of the outlets. 缓存用于存储插座的地图点。 The set of outlets is quite stable, but there should be a UI to add them, so insert operations are really rare. 这组插座非常稳定,但应该有一个UI来添加它们,因此插入操作非常少见。 It might be even easier to retrieve whole collection from the underlying layer after timeout then perform insert operations. 在超时后从底层执行整个集合然后执行插入操作可能更容易。 Search is not required as well, reading means simple enumeration. 也不需要搜索,阅读意味着简单的枚举。

Based on the comments to the question, I decided to use MemoryCache (whereof, in fact, I was not quite aware). 根据对问题的评论,我决定使用MemoryCache (事实上​​,我并不是很清楚)。 Since I don't need separated cache instances for data, I use cache that is kept in Default property: 由于我不需要为数据分离缓存实例,因此我使用保存在Default属性中的缓存:

Do not create MemoryCache instances unless it is required. 除非需要,否则不要创建MemoryCache实例。 (...) If you create only a single cache instance in your application, use the default cache and get a reference to it from the Default property when you need to access the cache. (...)如果在应用程序中只创建一个缓存实例,请使用默认缓存,并在需要访问缓存时从Default属性获取对它的引用。

Link (MSDN) 链接(MSDN)

Initializing of cache I made in the following way: 我通过以下方式初始化缓存:

private static MemoryCache _instance = MemoryCache.Default;

public static IEnumerable<MyDto> GetDtos()
{
    var result = _instance.Get(SOME_NAME);
    if (result == null)
        result = InitializeCache();
    return result;
}

No locks for reading operations. 没有用于阅读操作的锁。 Then, one lock in InitializeCache to prevent multiple requests: 然后,在InitializeCache锁定一个以防止多个请求:

private static IEnumerable<MyDto> InitializeCache()
{
    lock (_monitor) // here all threads but one will stop
    {
        var result = _instance.Get(SOME_NAME); // in case this thread is not the first who entered the lock
        if (result == null)
        {
            result = RetrieveSomeHowYourDataFromDbOrService();
            if (result == null)
                return null; // or exception
            _instance.Set(SOME_NAME, result, new CacheItemPolicy { AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddSeconds(TIMEOUT)), RemovedCallback = CacheClearedCallback}); 
        }
        return (IEnumerable<MyDto>)result;
    }
}

Use of CacheItemPolicy allows both clear out-of-date item and retrieve items again from the callback: 使用CacheItemPolicy允许清除过时的项目并从回调中再次检索项目:

private static void CacheClearedCallback(CacheEntryRemovedArguments arguments)
{
    InitializeCache();
}

In this case cache will be updated without request from the consumer (not as it is made in the first request in application). 在这种情况下,缓存将在没有来自消费者的请求的情况下进行更新(而不是在应用程序中的第一个请求中进行)。

Probably, this is not the best solution ever, but hope will help somebody, maybe to start with. 可能,这不是有史以来最好的解决方案,但希望可以帮助某人,也许可以从一开始。 Thanks for tips and comments. 感谢您提示和评论。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM