0

Please give me a hint about the following issue:

ConcurrentBag<string> test = null;
List<string> list= new() { "1", "2", "3", "2", "3", "2", "3", "2", "3", "2",
    "3", "2", "3", "2", "3", "2", "3", "2", "3", "2", "3", "2", "3" };
Parallel.ForEach(list, x => { (test ??= new()).Add("result"); });

=> test will have a different value each time when debugging (e. g. 19 / 23 entries)

ConcurrentBag<string> test = new();
List<string> list= new() { "1", "2", "3", "2", "3", "2", "3", "2", "3", "2",
    "3", "2", "3", "2", "3", "2", "3", "2", "3", "2", "3", "2", "3" };
Parallel.ForEach(list, x => { (test ??= new()).Add("result"); });

=> test will always have 23 entries

Why does the Parallel.ForEach not properly add the values when it's instantiated with null?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Trekki
  • 93
  • 6
  • Since it's running in parallel, some threads are encountering a `null` and re-instantiating the `ConcurrentBag` and adding it's value, thus it gets overwritten and in some cases you get less than 23 entries. Hence, when you set it to `new`, each thread doesn't encounter `null` and adds its value giving you the expected result. – Ryan Wilson Mar 21 '23 at 14:53
  • What you are experiencing is often referred to as a "race condition" - [what-is-a-race-condition](https://stackoverflow.com/questions/34510/what-is-a-race-condition#:~:text=A%20race%20condition%20occurs%20when,to%20access%20the%20shared%20data.) – Ryan Wilson Mar 21 '23 at 15:01
  • @RyanWilson ty, was already guessing something like that – Trekki Mar 21 '23 at 15:34
  • No problem. Glad that helped clear it up for you. – Ryan Wilson Mar 21 '23 at 15:40

1 Answers1

3
test ??= new()

Is functionally equivalent to:

if (test == null)
    test = new();
    

You are using multiple threads, so consider this scenario:

  1. Thread 1 reaches the if and sees that test is null. It therefore is about to go on to the next line, when it is pre-empted by thread 2
  2. Thread 2 reaches the if and sees that test is null. It therefore is goes on to create a new ConcurrentBag and assign it to test.
  3. Thread 1 now continues and creates a new ConcurrentBag and assigns it to test, overwriting the one created by thread 2.

Now you have two instances of ConcurrentBag in use, but only one is assigned to test.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276