SynchronizedCollection InvalidOperationException / System.ArgumentException - PullRequest
2 голосов
/ 23 апреля 2019

Я написал некоторый класс для тестирования многопоточности, используя 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 Class

Предоставляет потокобезопасную коллекцию, доступную только для чтения, которая содержит объекты тип, указанный универсальным параметром в качестве элементов.

1 Ответ

1 голос
/ 24 апреля 2019

Основная причина ошибки заключается в том, что конструкция List<T> не является поточно-ориентированной.

Давайте посмотрим, что происходит при создании новой SynchronizedReadOnlyCollection.Исключение возникает в следующей строке:

return new SynchronizedReadOnlyCollection<int>(testlist.SyncRoot, testlist);

Как сообщает StackTrace, в процессе построения задействовано 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 . Я очистил ненужные части кода для удобства чтения.Обратите внимание, что между комментариями (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);
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...