首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >有关缓存线程安全IEnumerable<T>实现的性能

有关缓存线程安全IEnumerable<T>实现的性能
EN

Stack Overflow用户
提问于 2009-07-06 15:47:41
回答 2查看 2.1K关注 0票数 4

我创建了ThreadSafeCachedEnumerable<T>类,目的是在长时间运行的查询被重用的情况下提高性能。这样做的目的是从IEnumerable<T>中获取一个枚举数,并在每次调用MoveNext()时将项添加到缓存中。以下是我目前的实现:

代码语言:javascript
复制
/// <summary>
/// Wraps an IEnumerable&lt;T&gt; and provides a thread-safe means of caching the values."/>
/// </summary>
/// <typeparam name="T"></typeparam>
class ThreadSafeCachedEnumerable<T> : IEnumerable<T>
{
    // An enumerator from the original IEnumerable<T>
    private IEnumerator<T> enumerator;

    // The items we have already cached (from this.enumerator)
    private IList<T> cachedItems = new List<T>();

    public ThreadSafeCachedEnumerable(IEnumerable<T> enumerable)
    {
        this.enumerator = enumerable.GetEnumerator();
    }

    public IEnumerator<T> GetEnumerator()
    {
        // The index into the sequence
        int currentIndex = 0;

        // We will break with yield break 
        while (true)
        {
            // The currentIndex will never be decremented,
            // so we can check without locking first
            if (currentIndex < this.cachedItems.Count)
            {
                var current = this.cachedItems[currentIndex];
                currentIndex += 1;
                yield return current;
            }
            else
            {
                // If !(currentIndex < this.cachedItems.Count),
                // we need to synchronize access to this.enumerator
                lock (enumerator)
                {
                    // See if we have more cached items ...
                    if (currentIndex < this.cachedItems.Count)
                    {
                        var current = this.cachedItems[currentIndex];
                        currentIndex += 1;
                        yield return current;
                    }
                    else
                    {
                        // ... otherwise, we'll need to get the next item from this.enumerator.MoveNext()
                        if (this.enumerator.MoveNext())
                        {
                            // capture the current item and cache it, then increment the currentIndex
                            var current = this.enumerator.Current;
                            this.cachedItems.Add(current);
                            currentIndex += 1;
                            yield return current;
                        }
                        else
                        {
                            // We reached the end of the enumerator - we're done
                            yield break;
                        }
                    }
                }
            }
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

当没有更多的项出现在缓存中时,我只是简单地lock (this.enumerator),以防另一个线程即将添加另一个项(我假设从两个线程调用this.enumerator上的MoveNext()是个坏主意)。

当检索以前缓存的项时,性能是很好的,但是当第一次获得许多项时(由于不断的锁定),性能就会受到影响。有什么提高绩效的建议吗?

编辑:新的反应性框架使用System.Linq.EnumerableEx.MemoizeAll()扩展方法解决了上述问题。

在内部,MemoizeAll()使用一个System.Linq.EnumerableEx.MemoizeAllEnumerable<T> (在System.Interactive程序集中找到),类似于MyThreadSafeCachedEnumerable<T>(排序)。

下面是一个非常精心设计的示例,它非常慢地打印可枚举的内容(数字1-10),然后很快第二次打印内容(因为它缓存了值):

代码语言:javascript
复制
// Create an Enumerable<int> containing numbers 1-10, using Thread.Sleep() to simulate work
var slowEnum = EnumerableEx.Generate(1, currentNum => (currentNum <= 10), currentNum => currentNum, previousNum => { Thread.Sleep(250); return previousNum + 1; });

// This decorates the slow enumerable with one that will cache each value.
var cachedEnum = slowEnum.MemoizeAll();

// Print the numbers
foreach (var num in cachedEnum.Repeat(2))
{
    Console.WriteLine(num);
}
EN

回答 2

Stack Overflow用户

发布于 2009-07-06 16:05:09

以下是几项建议:

  1. 现在人们普遍接受的做法是不让容器类负责锁定。例如,调用缓存枚举数的人也可能希望在枚举时防止将新条目添加到容器中,这意味着锁定将发生两次。因此,最好将这一责任推迟到调用方。
  2. 您的缓存依赖于枚举数总是按顺序返回项,这是不保证的.最好使用DictionaryHashSet。类似地,在调用之间可以删除项,从而使缓存无效。
  3. 通常不建议对可公开访问的对象建立锁。它包括包装枚举数。异常是可以想象的,例如,当您绝对确定您是唯一一个保存对您所枚举的容器类的引用的实例时。这也将在很大程度上使我在第二条下的反对意见无效。
票数 7
EN

Stack Overflow用户

发布于 2009-07-06 16:29:27

在.NET中锁定通常非常快(如果没有争用的话)。分析是否将锁定确定为性能问题的根源?在基础枚举数上调用MoveNext需要多长时间?

此外,目前的代码并不是线程安全的。您不能安全地在一个线程上调用this.cachedItems[currentIndex] (在if (currentIndex < this.cachedItems.Count)中),而在另一个线程上调用this.cachedItems.Add(current)。来自清单(T)文件:“一个列表(T)可以同时支持多个读取器,只要集合不被修改。”为了确保线程安全,您需要使用锁保护对this.cachedItems的所有访问(如果有可能一个或多个线程可以修改它)。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1087726

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档