3

I'm looking for a concurrent collection which can be accessed from both sides. I want to achieve the following:

  • Some producers add items
  • The client shall be able to display the last n produced items
  • The collection shall only contain elements produced within the last x hours

So I need to access the top of the list (FIFO) for displaying the last n items, but I also need to access the end of the list (LIFO) to prune elements older than x hours continuously.

8
  • Why you cannot use both? Wrap them to one class with Add method, which will add given item to both Commented May 1, 2018 at 8:17
  • @Fabio: You will have to synchronize access to two individual collections using some kind of locking mechanism but then you might just as well use a single list guarded by a lock which should be a solution to the problem. However, this question specifically mentions a concurrent collection. Commented May 1, 2018 at 8:20
  • @Fabio The Stack would fill up and there would be no way to remove old items. Commented May 1, 2018 at 8:25
  • A FIFO data structure always has its oldest item at the beginning. So the first item in the queue is the oldest not the last. To remove items that exceed a certain timespan you would have to validate each item you dequeue before display or remove the range of items periodically from head to tail. Have you checked out the BlockingCollection which allows the producer to signal end of production? msdn.microsoft.com/en-us/library/dd287247(v=vs.110).aspx Commented May 1, 2018 at 8:31
  • 1
    @ManuelFaux, you can use MemoryCache for removing "expiring" items Commented May 2, 2018 at 11:09

2 Answers 2

3

Another approach is to use MemoryCache class, which is thread-safe and provide built-in functionality for removing "expired" items.

I have created a class for keeping saved value and timestamp

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

    public T Value { get; set; }
}

Collection class will have two methods: one for adding and one for retrieving N-amount of last items

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);
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

For performance I would use "SkipWhile()" since "Where()" iterates over the full collection to compare each item. "ConcurrentBag" is unordered. This would require to search through the whole bag to find the item that was added last by comparing dateTime of each. If you would use a list or array you would just take from the end to get the latest. Now you are able to move linear through the collection using SkipWhile(). And Take() or TakeWhile() do not modify the source collection. You have to remove the items by yourself or your backupcollection will contain outdated data and grow and slow down
@BionicCode, updated answer with another approach which should handle removing "expired" values.
2

The client shall be able to display the last n produced items

That is not about removing items. I assume you want them to stay in there after having been displayed. So you don't really want the LIFO part.

When the number of items isn't too big you can use a ConcurrentQueue and ToArray() to get a snapshot (and use only the first n items).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.