#c# #list #asynchronous #concurrency #synchronization
#c# #Список #асинхронный #параллелизм #синхронизация
Вопрос:
Я написал несколько классов для тестирования многопоточности с помощью 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();
}
}
для его запуска я использую
MultithreadTesting test = new MultithreadTesting();
while (true)
test.Test();
Но код бросает меня System.ArgumentException: 'Destination array was not long enough. Check destIndex and length, and the array's lower bounds.'
Если я попытаюсь использовать testlist
в foreach
, я получу
System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'
Однако MSDN сообщает нам
Класс SynchronizedReadOnlyCollection
Предоставляет потокобезопасную коллекцию, доступную только для чтения, которая содержит объекты типа, указанного универсальным параметром в качестве элементов.
Ответ №1:
Основная причина ошибки заключается в том, что List<T>
построение не является потокобезопасным.
Давайте посмотрим, что происходит при создании нового SynchronizedReadOnlyCollection
. Исключение возникает в следующей строке:
return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);
Как сообщает нам трассировка стека исключений, в процессе построения List<T>..ctor
участвует:
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)
Следующий фрагмент из List<T>
конструктора показывает, где происходит ошибка. Код скопирован из MS reference source, я убрал ненужные части кода для облегчения чтения. Пожалуйста, обратите внимание, что между комментариями (1) и (2) есть другие потоки, манипулирующие сбором:
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;
}
}
Решение
Проблема может быть легко устранена с помощью testlist
модификации блокировки при создании нового SynchronizedReadOnlyCollection
.
public SynchronizedReadOnlyCollection<int> pubReadOnlyProperty
{
get
{
lock (testlist.SyncRoot)
{
return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);
}
}
}