Reputation: 73
I wrote some classes to test multithreading using SynchronizedCollection
.
class MultithreadTesting
{
public readonly SynchronizedCollection<int> testlist = new SynchronizedCollection<int>();
public SynchronizedReadOnlyCollection<int> pubReadOnlyProperty
{
get
{
return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);
}
}
public void Test()
{
int numthreads = 20;
Thread[] threads = new Thread[numthreads];
List<Task> taskList = new List<Task>();
for (int i = 0; i < numthreads / 2; i++)
{
taskList.Add(Task.Factory.StartNew(() =>
{
for (int j = 0; j < 100000; j++)
{
testlist.Add(42);
}
}));
}
for (int i = numthreads / 2; i < numthreads; i++)
{
taskList.Add(Task.Factory.StartNew(() =>
{
var sum = 0;
foreach (int num in pubReadOnlyProperty)
{
sum += num;
}
}));
}
Task.WaitAll(taskList.ToArray());
testlist.Clear();
}
}
to run it I use
MultithreadTesting test = new MultithreadTesting();
while (true)
test.Test();
But the code throws me System.ArgumentException: 'Destination array was not long enough. Check destIndex and length, and the array's lower bounds.'
If I try to use testlist
in foreach
, I get
System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'
However, MSDN tells us
SynchronizedReadOnlyCollection Class
Provides a thread-safe, read-only collection that contains objects of a type specified by the generic parameter as elements.
Upvotes: 3
Views: 345
Reputation: 3009
The root cause of the error is that List<T>
construction is not thread-safe.
Let's see what happens when constructing new SynchronizedReadOnlyCollection
. Exception occurs in following line:
return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);
As exception StackTrace tells us, there is List<T>..ctor
involved in construction process:
at System.Collections.Generic.SynchronizedCollection`1.CopyTo(T[] array, Int32 index)
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Collections.Generic.SynchronizedReadOnlyCollection`1..ctor(Object syncRoot, IEnumerable`1 list)
Following snippet from List<T>
constructor shows where error happens. Code is copied from MS reference source, I cleaned unnecessary parts of code for easier reading. Please notice that between comments (1) and (2) there are other threads manipulating collection:
public List(IEnumerable<T> collection) {
ICollection<T> c = collection as ICollection<T>;
// (1) count is now current Count of collection
int count = c.Count;
// other threads can modify collection meanwhile
if (count == 0)
{
_items = _emptyArray;
}
else {
_items = new T[count];
// (2) SynchronizedCollection.CopyTo is called (which itself is thread-safe)
// Collection can still be modified between (1) and (2)
// No when _items.Count != c.Count -> Exception is raised.
c.CopyTo(_items, 0);
_size = count;
}
}
Solution
The problem can easily be fixed with locking testlist
modification while constructing new SynchronizedReadOnlyCollection
.
public SynchronizedReadOnlyCollection<int> pubReadOnlyProperty
{
get
{
lock (testlist.SyncRoot)
{
return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);
}
}
}
Upvotes: 2