|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
||||||
System.InvalidOperationException: "Коллекция была изменена после создания экземпляра перечислителя."30.03.2019, 03:25. Показов 3289. Ответов 16
Метки нет (Все метки)
Имеется две вебсокет подписки, где data1 и data2 - часто приходящие данные типа SortedDictionary<decimal, decimal>. Во второй подписке обращаясь к первому элементу словаря иногда ловлю ошибку System.InvalidOperationException. Конструкция lock почему-то не синхронизирует доступ к dic. Почему такое случается? Что делаю не так?
0
|
||||||
| 30.03.2019, 03:25 | |
|
Ответы с готовыми решениями:
16
Коллекция была изменена после создания экземпляра перечислителя Max() ругается что коллекция была изменена |
|
Модератор
|
||
| 30.03.2019, 11:09 | ||
|
Действие конструкции распространяется только на её тело. А у Вас в теле lock только запись выражения LINQ по коллекции dic1. Эта коллекция создаётся в другом lock. Блокировка lock создаёт очередь из потоков обращающихся к переменной. Если за время обработки sub1 произойдёт обращение из другого потока, то он будет ждать в очереди. После разблокировки доступ получит следующий из очереди. А при повторной блокировке для sub2 этот поток станет в очередь последним. Все потоки которые его ждали будут выполнены до него. Что бы такого не было sub1 и sub2 должны вычисляться в одном lock.
1
|
||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
|||||||||||||
| 30.03.2019, 14:57 [ТС] | |||||||||||||
Т.е., я думал, что когда программа "зашла" в тело блокировки (в подписке sub2), то в другом потоке (в подписке sub1) программа "не зайдет" в конструкцию
На счёт очередности обращения вроде всё понимаю.
0
|
|||||||||||||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
|
| 30.03.2019, 15:28 [ТС] | |
|
Накидал небольшой пример, повторяющий мою проблему в целом. Что-то где-то я недопонимаю.. Подскажите, пожалуйста где мои действия неверны.
0
|
|
|
Модератор
|
||||||||||||
| 30.03.2019, 16:21 | ||||||||||||
Правильно так (если позволяет алгоритм)
1
|
||||||||||||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
||
| 30.03.2019, 16:36 [ТС] | ||
|
0
|
||
|
Модератор
|
|||||||||||||||||||||||
| 30.03.2019, 16:59 | |||||||||||||||||||||||
|
Ошибки в нём не возникает. Насколько понял проблема в этом участке
Попробуйте так:
Если пойму его смысл, возможно, найду другое решение. Добавлено через 6 минут А другой поток останавливается в очереди только если он хочет обратиться к этой переменной. Хотя бы вот так делайте
То есть должно быть какое-то обращение к блокированной переменной. Так как очередь блокировки возникает именно при обращении к этой переменной. Добавлено через 39 секунд Допустим попробуйте так
Но первая блокировка смысла не имеет. Присвоение ссылки другой поток никак нарушить не может.
1
|
|||||||||||||||||||||||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
||||||||||||||||||||||||||
| 30.03.2019, 22:05 [ТС] | ||||||||||||||||||||||||||
|
Я совсем запутался теперь..
Вами предложенное решение делать так:
Я также обращаюсь из других потоков к переменной dic1 в цикле foreach таким образом:
Суть: Имеется биржа. subscribe - это websocket подписка на обновление ордеров в стакане (удаление, замена, запись и т.д.) Это я симитировал в коде
Имеется несколько потоков, которые пользуются этими данными (Нужно вытащить актуальную цену, посчитать объем и т.д.) Это как раз я и пытался сделать с помощью
0
|
||||||||||||||||||||||||||
|
Модератор
|
||||||||
| 31.03.2019, 01:42 | ||||||||
|
Потокобезопасно, не потококобезопасно - разницы нет, если, в принципе, объём вычислений такой, что обработка не успевает за сообщениями сервера. Сервер биржи присылает сообщения. В каждом сообщении (если оно с данными) есть штамп времени. Если сообщений много, то учтите, что вполне возможен вариант когда старые сообщения приходят позже новых. Так как каждое сообщение имеет случайное время прохождения по интернету. Я тоже сейчас пытаюсь с BitMex разобраться. Так там порой разница в задержке бывает до 50 миллисекунд. А штамп времени порой вообще у 2-5 сообщений одинаковый. Приходится делать промежуточный буфер сообщений сервера с накоплением в 50 мс. А из буфера выбирать сообщения для обработки анализируя их тип и штамп времени. После того как сообщения выстроены в том порядке как создавались, их надо обрабатывать последовательно. Ни каких параллельных потоков здесь не будет. Пример. У Вас список Ордеров. Приходит метод iserpt - вставка нового Ordera. После него сразу (иногда с тем же штампом времени) метод update - изменяющий этот Order. Вот теперь представьте, если обрабатывать это в параллельных потоках. Скорость каждого потока не предсказуема. Поэтому даже, если Вы создадите поток для update позже чем поток для iserpt, всё равно, update может выполниться раньше. А что update может изменить в коллекции ордеров? Ведь ордера, который он должен изменить, в коллекции ещё не создано! Будете ловить непредсказуемые случайные баги. Я каждую подписку (то есть фактически каждый набор данных) обрабатываю в одном потоке. Вернее в очереди потоков - потоков много, но выполняются они последовательно друг за другом. А разные подписки могут обрабатываться в разных параллельных потоках, так как наборы данных у них независимы. Это только Локальная Модель. А потом ещё всё это надо синхронизировать в ViewModel и вывести в основном потоке UI элементов. Если же биржа большая и книга ордеров большая, то изменения очень частые и большие. Это легко может превысить возможности типичного домашнего или офисного компьютера. В сообщениях от сервера может быть много ненужной информации её можно полностью или частично отсеивать каким-нибудь простым быстроработающим фильтром. Но конкретных рекомендаций быть не может. Это всё очень индивидуально и зависит от очень многих причин. Добавлено через 25 минут Метод .ToArray() - это тоже метод расширения LINQ. Есть маленькая вероятность, что во время его исполнения коллекция измениться и получите баг. Причём баг может быть "неуловимый" и "неопределяемый". То есть данные у не правильные, но приложение этого не замечает и работает с неверными данными. А Вы создаёте приложение для Биржи - для работы с реальными деньгами! Такие баги могут быть очень опасны, так как пользователь может попасть на реальные деньги. Как минимум надо массив получать в заблокированном состоянии. Что-то в таком духе
Но это надо по алгоритму обработки смотреть может ли быть изменение элемента коллекции вол время цикла и может ли это привести к неправильным результатам. В данном случае KeyValuePair - это структура, то есть значимый тип. При таком создании дубля каждый элемент KeyValuePair тоже скопируется по значению. И экземпляры в массиве будут хоть и копиями, но не теми же что в исходном словаре. Но в другом случае, если в исходной коллекции классы, то экземпляры, при таком копировании, будут одни и те же. Скопированы будут только ссылки на них. И изменение экземпляра в параллельном потоке моет привести к неверным результатам. Добавлено через 9 минут DEMON_RUS, и я не получаю бага как на Ваших скринах. Но и на консоль ничего приложение не выводит. Может Вы что-то не выложили в тему? Добавлено через 4 минуты Странно.... Прогнал в пошаговой отладке - вроде заработало... Добавлено через 5 минут Выдаёт набор чисел, но не всегда. В трети случаев (примерно) - пустая консоль. Запускал раз двадцать. Баг ни разу не выходил.
1
|
||||||||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
|||
| 31.03.2019, 07:36 [ТС] | |||
|
Набор чисел - это некая имитация выдаваемых сформированных ордерБуков из вебсокета. Раз Вы в теме, накидаю аналогичный пример для бинанса, чтоб показать, какого результата я хочу добиться. На счёт ужаса, который творится на BitMex - на других биржах я вроде такого благо не встречал (бинанс, гейт, кукоин). Для этих бирж использую хороший пример с официальной документации бинанса Только немного его переделав для удобства использования. Добавлено через 45 секунд
0
|
|||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
|
| 31.03.2019, 09:07 [ТС] | |
|
Накидал пример на основе бинанса, там приведены все мои проблемы, с которыми не могу разобраться..
Основная суть - в сторонних задачах (привел примеры в коде) мне необходимо всячески пользоваться ордерБуками, сформированными в методе BuildLocalDepthCache (ордерБуки реалтайм обновляются сокетами). Подскажите, пожалуйста, какие ошибки я допускаю при таких подходах?
0
|
|
|
Модератор
|
||||||||||
| 31.03.2019, 14:22 | ||||||||||
|
Надо приложение делать сразу с учётом возможного наложения потоков, а не просто реагировать на проявление того или иного бага. Работая с перечислителем (а это и foreach, и LINQ) надо учитывать То есть надо обеспечить неизменность коллекции перечислитель которой используется. Для этого надо либо блокировать коллекцию от изменений (ReaderWriterLockSlim), либо делать копию коллекции (в том числе через LINQ метод ToArray). Добавлено через 8 минут Она-то нормально выдаёт сообщения. Но сообщения идут через инет, через чёрт знает сколько промежуточных узлов. И у каждого сообщения возникает индивидуальная задержка. Задержка эта случайна (в определённых рамках, конечно). У меня реально разница, хоть и редко, порой доходит до нескольких десятков миллисекунд. Между соседними сообщениями (они идут по одному пути) несколько единиц миллисекунд. Добавлено через 49 минут По коду первого Вашего теста. Ещё раз объясню, может не ясно выразил: 1) Блокировать locker бессмысленно. Потоки останавливаются при попытке доступа к заблокированному объекту. Обратите внимание ОБЪЕКТУ, а не ССЫЛКЕ! Объект (экземпляр) locker это пустой new object(). Он ни где не используется. Поэтому никакой блокировки по locker быть не может. Считайте что этих строк у Вас нет, они игнорируются при исполнении.2) В первом sub1 = subscribe.... у Вас происходит изменение ССЫЛКИ, а не ОБЪЕКТА здесь тоже ни какой блокировки в принципе не может быть. Поэтому здесь блокировка, вообще, не нужна.3) В методе subscribe у Вас первым идёт оператор Random rnd = new Random();. Разница (во времени) между двумя вызовами subscribe очень мала. А особенность new Random() это, то что он при вызовах подряд будет выдавать одинаковую последовательность. Вы, по-моему, этого не учитывали. Или у Ваш комп чем-то перегружен и между вызовами subscribe проходит достаточно времени для разной инициализации Random. Или это не важно для вашего примера.Время, точно не помню, между вызовами должно быть > 20 мс. 4)Во втором Action Вы используете коллекцию Bids. Блокировать надо именно её. И не только здесь, но и в потоке где она изменяется, то есть в методе subscribe.Так же перед получением Last надо проверять коллекцию Bids на пустоту иначе возможно исключение (у меня вылетает иногда по этой причине) или гарантировать, что она заполнена. Пустой она может быть если последний элемент удалён в методе subscribe.5) После моих корректировок вот полученный код:
Оператор lock проще, но при большом количестве потоков, чтобы не создавать лишних "тормозов" лучше использовать ReaderWriterLockSlim. Добавлено через 9 минут И у Вас непривычный мне стиль программирования. Какие-то бесконечные циклы... Должно быть так: 1) Часть приложения обрабатывающая сообщения от сервера. При внесении имения в данные вызывается событие извещающее об этом. 2) Часть приложения обрабатывающая имения в данных. Она подписана на событие в первой части. При возникновении события, проверяет что за изменения и вызывает, соответствующий, собственный метод. 3) Часть отвечающая за отображение данных и их подготовку к отображению. Она реагирует на изменения во второй части. Первые две относятся к локальной Mодели. Их можно сделать в разных классах или в одном. Третья часть это View и ViewModel.
1
|
||||||||||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
||||
| 01.04.2019, 15:56 [ТС] | ||||
|
0
|
||||
|
Модератор
|
||||||||
| 02.04.2019, 01:03 | ||||||||
Сообщение было отмечено DEMON_RUS как решение
РешениеПеред тем как получить доступ к определённому участку кода - используется блокировка-заглушка. А где в Вас доступ к данным? Он у Вас у внутри метода subscribe, а заглушка стоит снаружи. Если так уж хотите реализовать через заглушку, то Вам надо передать ссылку на неё внутрь метода и там реализовывать блокировку по заглушке. На мой взгляд, "притянуто за уши". Я сейчас попробую такой вариант накидать, но он мне концептуально очень не нравится. В данном случае, так как метод внутренний можно использовать приватное поле уровня класса. Но это по сути "костыль": - А если надо вызвать метод из другого класса? - Придётся блокировать полностью все потоки, а не только обращения к изменяемым данным. - Невозможно будет сделать блокировку только для записи. Ведь читающие потоки смысла блокировать, в данном случае, нет. Добавлено через 2 минуты
Видите, что получается? У Вас блокировка нужна в двух методах: subscribe и в анонимном Action. Добавлено через 4 минуты Поэтому большую часть информации надо получать по websocket. По REST идёт только отправка ордеров.
1
|
||||||||
|
3 / 2 / 1
Регистрация: 13.05.2013
Сообщений: 234
|
||
| 02.04.2019, 12:55 [ТС] | ||
|
Вроде стало понятнее всё, разобрался, спасибо)
0
|
||
|
Модератор
|
|||
| 02.04.2019, 15:27 | |||
|
REST очень накладный (для сервера) в этом отношении. При более мене значимом количестве клиентов REST не справится.
0
|
|||
|
10 / 1 / 0
Регистрация: 06.08.2018
Сообщений: 8
|
|
| 04.04.2019, 21:56 | |
|
Элд Хасп, Тут очень грамотно реализована система. https://github.com/Marfusios/b... -websocket
1
|
|
| 04.04.2019, 21:56 | |
|
Помогаю со студенческими работами здесь
17
Невозможно выполнить операцию перечисления. Коллекция была изменена
"Коллекция была изменена" - там, где она не была изменена Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |
|
Новые блоги и статьи
|
|||
|
[Owen Logic] Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ФедосеевПавел 14.03.2026
Поддержание уровня воды в резервуаре количеством включённых насосов: моделирование и выбор регулятора
ВВЕДЕНИЕ
Выполняя задание на управление насосной группой заполнения резервуара,. . .
|
делаю науч статью по влиянию грибов на сукцессию
anaschu 13.03.2026
прикрепляю статью
|
SDL3 для Desktop (MinGW): Создаём пустое окно с нуля для 2D-графики на SDL3, Си и C++
8Observer8 10.03.2026
Содержание блога
Финальные проекты на Си и на C++:
hello-sdl3-c. zip
hello-sdl3-cpp. zip
Результат:
|
Установка CMake и MinGW 13.1 для сборки С и C++ приложений из консоли и из Qt Creator в EXE
8Observer8 10.03.2026
Содержание блога
MinGW - это коллекция инструментов для сборки приложений в EXE. CMake - это система сборки приложений. Здесь описаны базовые шаги для старта программирования с помощью CMake и. . .
|
|
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд.
Даже если у вас. . .
|
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает
монорепозиторий в котором находятся все исходники.
При создании нового решения, мы просто добавляем нужные проекты
и имеем. . .
|
Модульный подход на примере F#
DevAlt 06.03.2026
В блоге дяди Боба наткнулся на такое определение:
В этой книге («Подход, основанный на вариантах использования») Ивар утверждает,
что архитектура программного обеспечения — это
структуры,. . .
|
Управление камерой с помощью скрипта OrbitControls.js на Three.js: Вращение, зум и панорамирование
8Observer8 05.03.2026
Содержание блога
Финальная демка в браузере работает на Desktop и мобильных браузерах. Итоговый код: orbit-controls-threejs-js. zip. Сканируйте QR-код на мобильном. Вращайте камеру одним пальцем,. . .
|