【问题标题】:Combined ConcurrentQueue and ConcurrentStack组合 ConcurrentQueue 和 ConcurrentStack
【发布时间】:2018-10-11 07:17:13
【问题描述】:

我正在寻找一个可以从双方访问的并发集合。我想实现以下目标:

  • 一些生产者添加项目
  • 客户端应该能够显示最后 n 个生产的项目
  • 集合应仅包含在过去 x 小时内生成的元素

所以我需要访问列表顶部 (FIFO) 以显示最后的 n 项,但我还需要访问列表末尾 (LIFO) 以修剪早于 x 小时连续。

【问题讨论】:

  • 为什么不能同时使用?使用Add 方法将它们包装到一个类中,这会将给定的项目添加到两者中
  • @Fabio:您将不得不使用某种锁定机制来同步对两个单独集合的访问,但是您也可以使用由锁保护的单个列表,这应该是解决问题的方法.但是,这个问题特别提到了一个并发集合
  • @Fabio 堆栈将填满,无法删除旧项目。
  • 一个 FIFO 数据结构总是在开头有它最旧的项。所以队列中的第一个项目是最旧的而不是最后一个。要删除超过特定时间跨度的项目,您必须在显示之前验证您出列的每个项目,或者定期从头到尾删除项目范围。您是否检查过允许生产者发出生产结束信号的 BlockingCollection? msdn.microsoft.com/en-us/library/dd287247(v=vs.110).aspx
  • @ManuelFaux,您可以使用 MemoryCache 删除“过期”项目

标签: c# .net multithreading collections


【解决方案1】:

另一种方法是使用MemoryCache 类,它是线程安全的,并提供删除“过期”项目的内置功能。

我创建了一个类来保存保存的值和时间戳

public class SavedItem<T>
{
    public DateTime Timestamp { get; set; }

    public T Value { get; set; }
}

Collection 类将有两种方法:一种用于添加,另一种用于检索最后 N 个项目

public class ExpiredCollection
{
    private readonly MemoryCache _cache;

    private readonly int _hoursLimit;

    public ExpiredCollection(int hoursLimit)
    {
        _cache = new MemoryCache("sample");
        _hoursLimit = hoursLimit;
    }

    public void Add<T>(T value)
    {
        var item = CreateCacheItem(value);
        var policy = CreateItemPolicy();

        _cache.Add(item, policy);
    }

    private CacheItem CreateCacheItem<T>(T value)
    {
        var addedValue = new SavedItem<T>
        {
            Timestamp = DateTime.Now,
            Value = value
        };
        // Create unique key to satisfy MemoryCache contract
        var uniqueKey = Guid.NewGuid().ToString();

        return new CacheItem(uniqueKey, addedValue);
    }

    private CacheItemPolicy CreateItemPolicy()
    {
        // This will set a time when item will be removed from the cache
        var expirationTime = DateTime.Now.AddHours(_hoursLimit);
        var offset = new DateTimeOffset(expirationTime);

        return new CacheItemPolicy
        {
            AbsoluteExpiration = offset
        };
    }

    public IEnumerable<T> GetLast<T>(int amount)
    {
        return _cache.Select(pair => (SavedItem<T>)pair.Value)
                     .OrderBy(item => item.Timestamp)
                     .Select(item => item.Value)
                     .Take(amount);
    }
}

【讨论】:

  • 为了提高性能,我会使用“SkipWhile()”,因为“Where()”会遍历整个集合以比较每个项目。 “ConcurrentBag”是无序的。这将需要通过比较每个包的 dateTime 来搜索整个包以找到最后添加的项目。如果您要使用列表或数组,您只需从最后获取最新的。现在您可以使用 SkipWhile() 在集合中线性移动。并且 Take() 或 TakeWhile() 不会修改源集合。您必须自己删除这些项目,否则您的备份集合将包含过时的数据并且会增长并变慢
  • @BionicCode,用另一种应该处理删除“过期”值的方法更新了答案。
【解决方案2】:

客户端应该能够显示最后 n 个生产的项目

这与删除项目无关。我假设您希望他们在展示后留在那里。 所以你并不真正想要 LIFO 部分。

当项目的数量不太大时,您可以使用 ConcurrentQueue 和 ToArray() 来获取快照(并且只使用前 n 个项目)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多