1

I want to execute two statements accessing a ConcurrentBag without any other thread accessing it in between. For example:

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);

how can that be done?

ispiro
  • 26,556
  • 38
  • 136
  • 291
  • Is `concurrentBag` local or member variable? If local than there is no way any other thread would know anything about it... Please clarify. – Alexei Levenkov Jun 23 '14 at 20:32
  • @AlexeiLevenkov This was just a simple example to explain what I'm talking about. It's a field. – ispiro Jun 23 '14 at 20:33
  • 1
    Don't make up 'simple examples'. They won't represent the complex reality. – H H Jun 23 '14 at 20:37
  • You'll have to make the `concurrentBag` private to whatever scope you want this in. – H H Jun 23 '14 at 20:38

3 Answers3

4

If you mean that you want to unconditionally block any other thread from accessing some object, then you simply can't do it. That is, if you have some object:

public Object foo = new Object();

There is nothing you can do in your thread to prevent other threads from accessing foo. A lock won't work, as you pointed out, because a lock is a cooperative mutual exclusion device. Nothing except convention prevents somebody from writing code that ignores the lock and accesses the object.

You could conceivably wrap the object in a class that enforces mutual exclusion, but then all you're doing is adding a lock or something similar. Even then, you'd have to create a special method that implements the semantics you're looking for (i.e. peek-and-add). And even that isn't foolproof because somebody could use reflection to access the underlying data structure directly.

Your peek-and-add operation is a bit unusual. I don't know enough about your application to say for sure, but in most cases there's a more conventional way to implement the functionality you want. This is particularly true with something like ConcurrentBag, where Peek will give different results depending on which thread is accessing it.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • I want a thread to be able to notify all other threads: "I'm doing blah blah blah so don't touch these variables" However, I first have to check that no other thread has asked for that yet. So I check the ConcurrentBag, and if no other thread has requested it - I request it. The problem being - that two threads might ask for it at the same time - both check and discover that no one has asked, and then both ask for it. Losing the point of the ConcurrentBag. – ispiro Jun 23 '14 at 20:37
  • 1
    @ispiro: Then use `ConcurrentDictionary` instead. It has the `TryAdd` method that returns false if the key already exists. Just figure out how to represent the notification within the key-value pair scheme of a dictionary. The `TryAdd` method can be called simultaneously from multiple threads, but only one thread will get a true result. – Brian Gideon Jun 23 '14 at 20:41
  • @BrianGideon Perfect. That's what I'm looking for. You can add that to your answer. Thanks. – ispiro Jun 23 '14 at 20:45
4

I suspect this is going to be significantly harder than just wrapping a few lines of code with the lock keyword. The reason being that you would have to wrap all access to the ConcurrentBag with a lock. And if you did that then it would not be "concurrent" anymore. I think what you are asking for is a way to temporarily suspend the concurrent behavior of the collection in favor of a serialized behavior and then resume its concurrent behavior once again when you have completed a special guarded operation.

The naive approach would be to create your own collection that provides the same operations as ConcurrentBag by using it internally as the underlying collection. You would then provide SuspendConcurrent and ResumeConcurrent operations that toggle the concurrent behavior of the collection by setting and resetting a flag (probably via the Interlocked.CompareExchange method). The code would then use this flag to determine whether to directly forward Add, TryPeek, and TryTake operations to the underlying collection or use a hard lock before doing so. I think in the end you will find the code very complex and incredibly hard to get right. Based on personal experience any trivial implementation you are envisioning right now will probably either be wrong, be inefficient, or not be fully concurrent when you want it to be.

My advice is to figure out a way to restructure your code so that this requirement goes away.

Edit:

From one of your comments it seems as though you want a CAS like operation to be able to make a decision based on whether or not something has already occurred. There are several options, but one which might best suit your needs is to use the ConcurrentDictionary class which provides the TryAdd method. TryAdd will return true if the key does not yet exist. If the key does exist then it will return false. Many threads can be executing this method simultaneously with the same key, but only one will get the true response. You can then use an if statement to control program flow based on the return value.

Brian Gideon
  • 47,849
  • 13
  • 107
  • 150
  • Thanks. See [my comment to Jim Mischel](http://stackoverflow.com/questions/24373936/block-a-concurrent-collection-such-as-a-concurrentbag-or-blockingcollection#comment37695178_24374733). – ispiro Jun 23 '14 at 20:41
-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);
}
dotnetom
  • 24,551
  • 9
  • 51
  • 54
  • Won't that just lock that **code**? What if there's other code that will try accessing the **object** `concurrentBag`? – ispiro Jun 23 '14 at 20:08
  • When you come to lock only single thread can access block locked by the same object. The first thread to acquire lock will have it until the lock block ends. Then the next thread can access the lock blocked by the same object. To be fair the locker should be static, so I updated my answer – dotnetom Jun 23 '14 at 20:10
  • 1
    @dotnetom No, the object shouldn't necessarily be static, first off. That is, not unless the state being accessed is static, which it's not. Next, this doesn't require code accessing the `concurrentBag` anywhere besides here to take out the lock, and if you can't solve that problem, this doesn't help. – Servy Jun 23 '14 at 20:31