ConcurrentBag
guarantees that each operation on it is thread-safe when considered on its own. It does not guarantee that multiple operations in succession will be treated as an atomic group.
As a result your code has a race condition: you check if the bag already contains some item X, but two threads can run the test concurrently, decide that the item isn't there, and proceed to add it. End result: two copies of the item end up being in the bag.
It looks like your use case would be better implemented by using a ConcurrentDictionary
instead and leveraging the TryAdd
method, which is atomic. Alternatively you could put a lock()
around the bag to make everything inside the block operate atomically, but then you don't really need a concurrent collection and can use a straight List
instead.