Concurrentdictionary is a collection class that is newly added in. Net 4.0 and claims to be thread-safe.
Naturally, we will expectCodeIt is thread-safe (at least several key methods are thread-safe ).
For example, the user may expect the method in getoradd to only execute the Add delegate once when the key does not exist. The second call to getoradd should retrieve the value just generated.
Refer to the following code:
Public Static Void Test ()
{
VaR Concurentdictionary = New Concurrentdictionary < Int , Int > ();
VaR W = New Manualresetevent ( False );
Int Timedcalled = 0 ;
VaR Threads = New List <thread> ();
For ( Int I = 0 ; I <environment. processorcount; I ++)
{
Threads. Add ( New Thread () =>
{
W. waitone ();
Concurentdictionary. getoradd ( 1 , I1 =>
{
Interlocked. increment ( Ref Timedcalled );
Return 1 ;
});
}));
Threads. Last (). Start ();
}
W. Set ();// Release all threads to start at the same time
Thread. Sleep ( 100 );
Console. writeline (timedcalled ); // Output is 4, means call initial 4 times
// Console. writeline (concurentdictionary. Keys. Count );
}
The getoradd method is defined to obtain a value based on the key. If the key does not exist, call func <t> to add a key-value pair.
According to the definition of concurrentdictionary, I expect this add should be called only once
However, the running result of the above Code shows that interlocked. increment (RefTimedcalled); 4 calls, really embarrassing
The delegate used for initialization value can also be executed multiple times, so
- You can also ensure that the code in the delegate is repeat without any problems.
- Either use a thread-safe initialization method, such as lazy <t>
Public Static Void Test ()
{
VaR Concurentdictionary = New Concurrentdictionary < Int , Int > ();
VaR W = New Manualresetevent ( False );
Int Timedcalled = 0 ;
VaR Threads = New List <thread> ();
Lazy < Int > Lazy = New Lazy < Int > () => {Interlocked. increment (Ref Timedcalled ); Return 1 ;});
For ( Int I = 0 ; I <environment. processorcount; I ++)
{
Threads. Add ( New Thread () =>
{
W. waitone ();
Concurentdictionary. getoradd ( 1 , I1 =>
{
Return Lazy. value;
});
}));
Threads. Last (). Start ();
}
W. Set (); // Release all threads to start at the same time
Thread. Sleep ( 100 );
Console. writeline (timedcalled ); // Output is 1
}
Note: The initialization method will be called multiple times. If this problem is not encountered by accident, it is estimated that the initialization method will never be known.
//
// Summary:
// Adds a key/value pair to the system. Collections. Concurrent. concurrentdictionary <tkey, tvalue>
// If the key does not already exist.
//
// Parameters:
// Key:
// The key of the element to add.
//
// Valuefactory:
// The function used to generate a value for the key
//
// Returns:
// The value for the key. This will be either the existing value for the key
// If the key is already in the dictionary, or the new value for the key
// Returned by valuefactory if the key was not in the dictionary.
//
// Exceptions:
// System. argumentnullexception:
// Key is a null reference (nothing in Visual Basic).-Or-valuefactory is a null
// Reference (nothing in Visual Basic ).
//
// System. overflowexception:
// The dictionary contains too extends elements.
All the methods using func <t> in this collection class also have similar problems.
I hope to remind friends who do not know the problem to avoid unnecessary bugs.