ConcurrentStack:如何检查是否包含对象?

时间:2018-09-27 10:45:37

标签: c#

如何检查堆栈中是否包含特定对象?

private ConcurrentStack<int> cs = new ConcurrentStack<int>();
cs.Push(1);

3 个答案:

答案 0 :(得分:2)

Stack<T>.Contains中没有方法ConcurrentStack<T>-class。我猜是因为那不是线程安全的。

因此,如果需要,则必须使用锁,然后可以使用Enumerable.Contains

private ConcurrentStack<int> cs = new ConcurrentStack<int>();
private Object csLockObject  = new Object();

...

bool contains = false;
lock (csLockObject)
{
    contains = cs.Contains(1);
}

但是,当枚举此快照时,另一个线程可能会向堆栈中添加或从堆栈中删除项目。如果您想防止这种情况,则还需要在添加/删除位置添加一个锁。

  

我要避免重复

好吧,您可以使用类似这样的类,该类使用ConcurrentDictionary来检查其是否唯一:

public class ConcurrentUniqueStack<T>
{
    private readonly ConcurrentDictionary<T, int> _itemUnique; // there is no ConcurrentHashSet so we need to use a Key-only dictionary
    private readonly ConcurrentStack<T> _stack;

    public ConcurrentUniqueStack() : this(EqualityComparer<T>.Default)
    {
    }
    public ConcurrentUniqueStack(IEqualityComparer<T> comparer)
    {
        _stack = new ConcurrentStack<T>();
        _itemUnique = new ConcurrentDictionary<T, int>(comparer);
    }

    public bool TryPush(T item)
    {
        bool unique = _itemUnique.TryAdd(item, 1);
        if (unique)
        {
            _stack.Push(item);
        }

        return unique;
    }
    public bool TryPop(out T result)
    {
        bool couldBeRemoved = _stack.TryPop(out result);
        if (couldBeRemoved)
        {
            _itemUnique.TryRemove(result, out int whatever);
        }
        return couldBeRemoved;
    }

    public bool TryPeek(out T result) => _stack.TryPeek(out result);
}

答案 1 :(得分:2)

此结构未针对此操作进行优化。

这意味着,值查找操作具有O(N)复杂性,因为您必须迭代整个集合。

所以答案取决于您的要求:

  • 如果您有时要检查值(例如,复杂性无关紧要),只需使用_stack.FirstOrDefault(v=> v==valueWhichYouTryToFind)。这可能很慢,因为您必须迭代所有元素。但是任务将会解决。并且请注意,可以在此函数完成之前更改堆栈(例如,该函数可以在true操作完成之后返回stack pop
  • 如果您想经常检查巨大堆栈中的值-那么您必须使用并发集+堆栈创建复杂的结构。我建议(这只是我的看法)只使用简单的Stack + HashSet +锁,因为它非常简单。

答案 2 :(得分:2)

您的问题没有简单的答案。原因是ConcurrentStack设计用于多线程环境,但有一些注意事项。关键问题是您的Contains操作必须是原子的。让我们探讨一下您拥有的选项:

1。枚举堆栈

可以使用foreach枚举堆栈,并检查您的值是否在其中。问题在于该枚举不是原子的,在多线程环境中甚至很危险,因为枚举中另一个线程的堆栈更改将导致InvalidOperationException。 LINQ方法Enumerable.Contains在这里没有什么区别,因为它还会枚举堆栈。

您可以简单地将枚举放在lock内,仅当 all 对堆栈的访问(即,push和pops)位于同一锁内时,该枚举才起作用。然后,您不需要ConcurrentStack。您可以简单地使用常规Stack。请注意,ConcurrentStackConcurrentQueueConcurrentBag是无锁实现的。将它们放在锁中可以消除此问题。

2。窥视堆栈顶部

您可以在堆栈上调用TryPeek,这是一个原子操作。问题在于您只会在堆栈顶部获得该值。如果这对您来说很好,那么您可以采用此解决方案。否则,你不能。

3。弹出并推送值

您可以TryPopRange从堆栈中获取所有值,然后TryPushRange将这些值返回堆栈中。我仅出于完整性考虑而添加了此选项,因为它实际上是完全疯狂的。当您从堆栈中弹出所有值时,它们将不再存在,直到您将它们推回。如果有人想弹出堆栈顶部,那么他们倒霉。更糟糕的是,当他们推送另一个值时,您不仅会在检查中错过它,而且还会动摇堆栈顺序。

这一切都意味着您必须再次将整个操作放在一个锁中,使用选项1(枚举堆栈)可以更轻松地完成此操作。

结论

如果只需要检查堆栈顶部,请使用TryPeek。如果需要检查所有值,请在每次访问它时使用Stack并将其放在锁中。