The concurrency of the ConcurrentDictionary
is what makes this not work.
The only opportunity you really have to act on the value already in the dictionary is in the updateValueFactory
, but that work will take place before the update actually happens and the value is set to true
. During this period, another thread may also attempt to AddOrUpdate
, in which case it will still see the old value of false
, and start the update logic again.
Here's a sample program to demonstrate this:
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace ConcurrentDictionaryCancelTest {
class Program {
static void Main( string[] args ) {
var example = new ConcurrentDictionary<string, bool>();
for( var i = 0; i < 3; i++ ) {
example.AddOrUpdate( i.ToString(), false, ( key, oldValue ) => false );
}
Parallel.For( 0, 8, x => {
example.AddOrUpdate(
( x % 3 ).ToString(),
( key ) => {
Console.WriteLine( "addValueFactory called for " + key );
return true;
},
( key, oldValue ) => {
Console.WriteLine( "updateValueFactory called for " + key );
if( !oldValue ) {
var guid = Guid.NewGuid();
Console.WriteLine(
key + " is calling UpdateLogic: " + guid.ToString()
);
UpdateLogic( key, guid );
}
return true;
}
);
} );
}
public static void UpdateLogic( string key, Guid guid ) {
Console.WriteLine(
"UpdateLogic has been called for " + key + ": " + guid.ToString()
);
}
}
}
And some sample output:
updateValueFactory called for 0
updateValueFactory called for 1
updateValueFactory called for 2
updateValueFactory called for 0
updateValueFactory called for 1
0 is calling UpdateLogic: cdd1b1dd-9d96-417d-aee7-4c4aec7fafbf
1 is calling UpdateLogic: 161c5f35-a2d7-44bf-b881-e56ac713b340
UpdateLogic has been called for 0: cdd1b1dd-9d96-417d-aee7-4c4aec7fafbf
updateValueFactory called for 1
1 is calling UpdateLogic: 6a032c22-e8d4-4016-a212-b09e41bf4d68
UpdateLogic has been called for 1: 6a032c22-e8d4-4016-a212-b09e41bf4d68
updateValueFactory called for 0
updateValueFactory called for 2
2 is calling UpdateLogic: 76c13581-cd55-4c88-961c-12c6d277ff00
UpdateLogic has been called for 2: 76c13581-cd55-4c88-961c-12c6d277ff00
1 is calling UpdateLogic: d71494b6-265f-4ec8-b077-af5670c02390
UpdateLogic has been called for 1: d71494b6-265f-4ec8-b077-af5670c02390
UpdateLogic has been called for 1: 161c5f35-a2d7-44bf-b881-e56ac713b340
updateValueFactory called for 1
updateValueFactory called for 1
0 is calling UpdateLogic: f6aa3460-444b-41eb-afc6-3d6afa2f6512
UpdateLogic has been called for 0: f6aa3460-444b-41eb-afc6-3d6afa2f6512
2 is calling UpdateLogic: d911dbd1-7150-4823-937a-26abb446c669
UpdateLogic has been called for 2: d911dbd1-7150-4823-937a-26abb446c669
updateValueFactory called for 0
updateValueFactory called for 2
Note the delay between the first time updateValueFactory
is called for 0, when UpdateLogic
is going to be called, and then when it actually executes. During this time, i.e. before the value is updated to true
, updateValueFactory
is called for 0 again, and this results in the UpdateLogic
being run for 0 again as well.
You need some kind of lock to make sure that reading the value, calling the update logic, and setting the new value is all one atomic operation.