Использование очереди для усреднения коллекции из 9 значений с плавающей запятой. Время от времени (обычно это работает!) Я получаю следующую ошибку
InvalidOperationException: Operation is not valid due to the current state of the object System.Linq.Enumerable.Average(IEnumerable`1 source)
Оскорбительная строка является последней в этом фрагменте кода.
private bool OnPersonUpdated(IEvent evt)
{
Event_Update castEvent = evt as Event_Update;
if (castEvent != null)
{
if (peopleDict.ContainsKey(castEvent.id))
{
float xVel = castEvent.velX;
GameObject cubeToMove = peopleDict[castEvent.id];
if (xVel > 0)
{
float xPos = -1f * positionForPerson(castEvent.person);
float dif = xPos - _prevX;
if (dif < .5f)
{
_posQueue.Enqueue(xPos);
}
if (_posQueue.Count >= 10)
{
_posQueue.Dequeue();
}
float avPos = _posQueue.Average();
Я понимаю, что без предоставления полного приложения трудно сказать, что происходит, но какие шаги по устранению неполадок мне следует попробовать?
Я обновил свой пост дополнительным кодом ...
Это происходит либо (1) потому, что ваша очередь изменяется одновременно с итерацией; это единственное условие, которое заставляет итератор очереди генерировать InvalidOperationException
, или (2) ваша очередь не имеет элементов, а тип элемента не допускает значения NULL.
(1) Рассмотрим исходный код ссылки для Queue<T>
, найденный здесь. Вот место в коде, генерирующее исключение (строка 369):
if (_version != _q._version) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
_q.version
обновляется при изменении экземпляра Queue<T>
; переменная экземпляра _version
класса Queue<T>.Enumerator
хранит версию Queue<T>
на момент начала итерации.
Вы можете решить эту проблему, добавив блокировку чтения и обновления или переключившись на использование ConcurrentQueue<T>
.
(2) Рассмотрим исходный код ссылки для Enumerable<T>
, найденный здесь. Код для усреднения float
(строка 2002) и float?
(строка 2016) отличается тем, как они обрабатывают пустые коллекции: версия, допускающая значение NULL, возвращает null
, а версия, не допускающую значения NULL, вызывает исключение. Вы можете обойти это, преобразовав элементы очереди в float?
перед усреднением:
float? avPos = _posQueue.Cast<float?>().Average();
Очевидно, это изменение требует от вас нулевой проверки avPos
.
Хотя в этом случае сообщение об исключении отличается от того, что было опубликовано OP. Кроме того, Average
выбрасывает InvalidOperationException
, когда очередь пуста (также с другим сообщением, чем отправленное).
Спасибо за большую помощь. Я думаю, что моя цель теперь должна быть очевидна из моего обновленного образца кода> постоянно усреднять коллекцию из 10 самых последних значений с плавающей запятой в цикле событий. Вместо этого попробую ConcurrentQueue ...
К сожалению, в моно-фреймворке, похоже, нет класса ConcurrentQueue в mscorlib :( answers.unity.com/questions/1375947/… Может ли кто-нибудь помочь с повторным «добавлением блокировки чтения и обновления» ??
@Evk Похоже, что mono добавила поддержку параллельных очередей несколько версий назад. Если вы используете более раннюю версию, вы можете взять эталонный код и использовать его в своем собственном проекте. Добавление блокировки потребует обхода всех мест, где вы используете _posQueue
, и добавления lock (_posQueue)
вокруг всего блока кода, который обращается к очереди.
ок, нашел кастомную очередь с блокировкой для Unity gist.github.com/jaredjenkins/5421892
Однако отсутствует метод среднего значения. Есть ли шанс, что кто-то может помочь? gist.github.com/jaredjenkins/5421892
@eco_bach Попробуйте _posQueue.CopyToArray().Average()
Если вы используете очередь, коллекция, скорее всего, будет использоваться другим ресурсом / потоком. Без более подробной информации об объекте очереди сказать невозможно. Но в сообщении об ошибке указывается, что объект находится в недопустимом состоянии, и это может означать, что объект был добавлен / удален, когда вы выполняли метод
Average
.