如何检查堆栈中是否包含特定对象?
private ConcurrentStack<int> cs = new ConcurrentStack<int>();
cs.Push(1);
答案 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
)答案 2 :(得分:2)
您的问题没有简单的答案。原因是ConcurrentStack
设计用于多线程环境,但有一些注意事项。关键问题是您的Contains
操作必须是原子的。让我们探讨一下您拥有的选项:
1。枚举堆栈
您可以使用foreach
枚举堆栈,并检查您的值是否在其中。问题在于该枚举不是原子的,在多线程环境中甚至很危险,因为枚举中另一个线程的堆栈更改将导致InvalidOperationException
。 LINQ方法Enumerable.Contains
在这里没有什么区别,因为它还会枚举堆栈。
您可以简单地将枚举放在lock
内,仅当 all 对堆栈的访问(即,push和pops)位于同一锁内时,该枚举才起作用。然后,您不需要ConcurrentStack
。您可以简单地使用常规Stack
。请注意,ConcurrentStack
,ConcurrentQueue
和ConcurrentBag
是无锁实现的。将它们放在锁中可以消除此问题。
2。窥视堆栈顶部
您可以在堆栈上调用TryPeek
,这是一个原子操作。问题在于您只会在堆栈顶部获得该值。如果这对您来说很好,那么您可以采用此解决方案。否则,你不能。
3。弹出并推送值
您可以TryPopRange
从堆栈中获取所有值,然后TryPushRange
将这些值返回堆栈中。我仅出于完整性考虑而添加了此选项,因为它实际上是完全疯狂的。当您从堆栈中弹出所有值时,它们将不再存在,直到您将它们推回。如果有人想弹出堆栈顶部,那么他们倒霉。更糟糕的是,当他们推送另一个值时,您不仅会在检查中错过它,而且还会动摇堆栈顺序。
这一切都意味着您必须再次将整个操作放在一个锁中,使用选项1(枚举堆栈)可以更轻松地完成此操作。
结论
如果只需要检查堆栈顶部,请使用TryPeek
。如果需要检查所有值,请在每次访问它时使用Stack
并将其放在锁中。