阻止并发集合,例如ConcurrentBag或BlockingCollection

时间:2014-06-23 19:35:27

标签: c# .net multithreading

我想执行两个访问ConcurrentBag的语句,而不需要任何其他线程访问它。例如:

ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
concurrentBag.Add(1);
int check = 0;
//Somehow block so that the following two statements will 
//be executed without any other thread accessing concurrentBag.
concurrentBag.TryPeek(out check);
if (check == 2) concurrentBag.Add(3);

怎么办呢?

3 个答案:

答案 0 :(得分:4)

如果您想要无条件地阻止任何其他线程访问某个对象,那么您根本无法做到。也就是说,如果你有一些对象:

public Object foo = new Object();

您可以在线程中执行 nothing 以防止其他线程访问foo。正如您所指出的,锁定无法正常工作,因为锁定是协作互斥设备。除了约定之外什么都不会阻止某人编写忽略锁并访问对象的代码。

你可以想象将对象包装在一个强制互斥的类中,但是你所做的只是添加一个锁或类似的东西。即便如此,您还必须创建一种特殊的方法来实现您正在寻找的语义(即查看和添加)。即使这不是万无一失,因为有人可以使用反射来直接访问底层数据结构。

你的偷看和添加操作有点不寻常。我不确定你的应用程序是否足够清楚,但在大多数情况下,实现所需功能的方式更为传统。对于类似ConcurrentBag的内容尤其如此,其中Peek将根据访问它的线程给出不同的结果。

答案 1 :(得分:4)

我怀疑这比使用lock关键字包装几行代码要困难得多。原因是您需要使用锁定来包含对ConcurrentBag所有访问权限。如果你这样做那么它就不会再“并发”了。我认为你要求的是一种临时暂停集合的并发行为以支持序列化行为的方法,然后在完成特殊的保护操作后再次恢复其并发行为。

天真的方法是创建自己的集合,通过在内部使用它作为底层集合,提供与ConcurrentBag相同的操作。然后,您将提供SuspendConcurrentResumeConcurrent操作,通过设置和重置标志(可能通过Interlocked.CompareExchange方法)来切换集合的并发行为。然后,代码将使用此标志来确定是否将AddTryPeekTryTake操作直接转发到基础集合,或者在执行此操作之前使用硬锁定。我想最终你会发现代码非常复杂,难以理解。根据个人经验,您现在想象的任何琐碎的实现都可能是错误的,效率低下的,或者在您想要的时候不完全并发。

我的建议是找出一种重构代码的方法,以免这个要求消失。

修改

从您的一条评论中,您似乎希望CAS like操作能够根据是否已经发生某些事情做出决定。有几个选项,但最适合您需求的选项是使用提供ConcurrentDictionary方法的TryAdd类。如果密钥尚不存在,TryAdd将返回true。如果密钥确实存在,那么它将返回false。许多线程可以使用相同的密钥同时执行此方法,但只有一个将获得真正的响应。然后,您可以使用if语句根据返回值控制程序流。

答案 2 :(得分:-1)

static object syncLock = new object();
ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
concurrentBag.Add(1);
int check = 0;
lock(syncLock)
{
    concurrentBag.TryPeek(out check);
    if (check == 2) concurrentBag.Add(3);
}