Это сбой из-за проблемы параллелизма во внутреннем методе ReceiveCurrent из MessageQueue.
Трассировка стека исключений показывает вызов, исходящий из enumerator.Current, а исключение произошло в ReceiveCurrent. Enumerator.Current вызывает ReceiveCurrent с опцией «peek». Вы можете спросить, что у меня было также, когда я столкнулся с той же самой проблемой, как может провал взгляда с ошибкой "Сообщение уже получено"? Он только пытается посмотреть следующее сообщение, которое еще не получено?
Ответ на этот вопрос лежит в коде ReceiveCurrent, который доступен для просмотра здесь:
https://referencesource.microsoft.com/#System.Messaging/System/Messaging/MessageQueue.cs,02c33cc512659fd7,references
ReceiveCurrent сначала выполняет вызов StaleSafeReceive для просмотра следующего сообщения. Но если этот вызов возвращает, ему нужно больше памяти для получения всего сообщения (строка с
«while (MessageQueue.IsMemoryError (status)» в своем исходном коде) выделяет необходимую память и выполняет другой вызов StaleSafeReceive для получения сообщения.
Это очень классический шаблон использования Win32 API, поскольку в конечном итоге он основан на C.
Проблема здесь в том, что если между первым и вторым вызовом StaleSafeReceive внутри ReceiveCurrent «получает» другой процесс или поток, т. Е. Удаляет это сообщение из очереди, второй вызов вызывает именно это исключение. И вот как операция «Peek» не удается.
Обратите внимание, что это может быть любое сообщение, которое просматривает перечислитель, вызвавший исключение, а не сообщение, которое ищется. Это объясняет, почему сообщение с таким идентификатором задания все еще находится в очереди после того, как возникла исключительная ситуация и метод завершился неудачей.
Что можно сделать, это защитить enumerator. Текущий вызов с помощью try catch, и если это конкретное исключение перехватывается, просто продолжайте перечисление со следующим доступным сообщением в очереди.
Я использовал объект Cursor, а не перечислитель, но он сталкивается с той же проблемой. Но при использовании курсора существует другой способ уменьшить риск этого, то есть при сканировании / просмотре сообщения отключить все ненужные свойства объекта MessagePropertyFilter текущего объекта очереди, особенно свойство Body. Поскольку во время просмотра тела обычно не требуется получать тело, но чаще всего тело сообщения вызывает перераспределение памяти и требует второго вызова StaleSafeReceive внутри ReceiveCurrent.
Тем не менее, попытка перехвата этого исключения потребовалась бы и при прямом использовании курсора с вызовами peek.