Обязан ли я вызывать EndPeek после использования BeginPeek?

У меня есть служба Windows, которая обрабатывает частную локальную очередь сообщений (MSMQ). При запуске он регистрирует обработчик событий для PeekCompleted в очереди, а затем вызывает асинхронный BeginPeek() для ожидания прибытия сообщения.

protected override void OnStart(string[] args)
{
    if (String.IsNullOrWhiteSpace(args[0]))
        return;

    queue = new MessageQueue(args[0]);
    queue.Formatter = new BinaryMessageFormatter();
    queue.PeekCompleted += new PeekCompletedEventHandler(OnPeekCompleted);

    queue.BeginPeek();
}

Когда приходит сообщение, моя цель, очевидно, состоит в том, чтобы обработать это сообщение. Мой код в настоящее время имеет метод queue.Receive() для получения сообщения, содержащегося в транзакции, поэтому сообщение возвращается в очередь в случае ошибок во время обработки. BeginPeek() вызывается снова, чтобы перезапустить цикл.

private static void OnPeekCompleted(Object source, PeekCompletedEventArgs asyncResult)
{
    try
    {
        MessageQueue q = (MessageQueue)source;

        using (MessageQueueTransaction trans = new MessageQueueTransaction())
        {
            trans.Begin();

            Message msg = q.Receive(trans);
            ProcessMessage(msg);

            trans.Commit();
         }

         // Restart the asynchronous peek operation.
         q.BeginPeek();
    }
    catch (MessageQueueException qEx)
    {
        // TODO: Walk it off.
    }
    return;
}

Нужно ли мне в какой-либо момент вызывать EndPeek() в очереди?

Может быть, чтобы избежать утечек памяти, например, этот вопрос намекает на? Я почти уверен, что мне это не нужно, но документация не очень ясна по этому поводу. Просто не кажется на 100% правильным «начать» что-то, не «закончив» это :)

Кстати: я мог бы заменить строку Receive() на Message msg = q.EndPeek(asyncResult.AsyncResult), которая в равной степени доставит мне сообщение, но не удалит сообщение из очереди.


person pleinolijf    schedule 18.11.2014    source источник
comment
Есть ли веская причина не звонить .EndPeek()? Если вам не нужен результат .EndPeek(), вы можете отказаться от него, но не вызывать его вообще почти наверняка плохая идея — вы, по сути, делаете ставку на то, что .BeginPeek() не выделяет никаких неуправляемых ресурсов, которые .EndPeek() должен освободить. Даже если сейчас это так, кто сказал, что следующий релиз не нарушит это предположение?   -  person Jeroen Mostert    schedule 18.11.2014
comment
Примечание: pleinolijf, ваш пост читается так, как будто мой код выглядит неправильно, есть шанс, что он будет работать надежно. Лично я бы не стал этого делать, даже если код работает правильно - время, потраченное на поиск/исправление отсутствующего вызова EndXXXX всеми будущими читателями кода, скорее всего, перевесит любую экономию, которую вы сделаете.   -  person Alexei Levenkov    schedule 18.11.2014
comment
Справедливые очки, вы оба. И это, по сути, то, о чем я спрашиваю: безопасно ли не вызывать EndPeek? Действительно ли BeginPeek выделяет какие-либо ресурсы? Вот что мне непонятно, хотя очевидно, что названия методов на это указывают. Я ищу факты, а не предположения.   -  person pleinolijf    schedule 18.11.2014
comment
Чтобы добавить к комментарию @JeroenMostert: см. мой последний абзац. Сообщение, похоже, осталось в очереди. Мне кажется нелогичным звонить сначала EndPeek(), а потом Receive() для верности...   -  person pleinolijf    schedule 18.11.2014
comment
Непонятно, какой факт вы преследуете. Вы хотите, чтобы дизайнер API объяснил вам, что именно они имели в виду? Конечно, если вы узнаете, что любая версия System.Messaging.dll, которую вы используете, имеет реализацию .BeginPeek(), которая распределяет или не выделяет ресурсы, ничего не доказывает, кроме того, что ваш код в настоящее время работает (не) работает.   -  person Jeroen Mostert    schedule 18.11.2014
comment
Да, излишне вызывать EndPeek(), а затем Receive(). Но это потому, что вы решили позвонить BeginPeek(), а не BeginReceive(). Считаете ли вы логичным, чтобы Receive() проверял все невыполненные BeginPeek() запросы, чтобы очистить их?   -  person Jeroen Mostert    schedule 18.11.2014
comment
Я хочу использовать BeginPeek(), чтобы фактическое получение сообщения можно было выполнить в рамках транзакции. Мне не удалось сделать это с BeginReceive() в такой настройке. Есть ли способ использовать EndPeek(), а затем удалить сообщение из очереди (без использования Receive()?   -  person pleinolijf    schedule 18.11.2014
comment
Не то, чтобы я мог видеть. Кроме того, в MSDN прямо говорится, что вы не можете использовать BeginReceive() с транзакциями, а вместо этого должны использовать BeginPeek(), так что все в порядке - однако затем не говорится, что вы можете не вызывать EndPeek(). Я по-прежнему не вижу проблемы — просто вызовите EndPeek(asyncResult), игнорируя результат, и покончим с этим. Это, скорее всего, даже не повлечет за собой каких-либо (предотвратимых) накладных расходов, поскольку вы все равно уже начали операцию просмотра - не завершение ее не устраняет ее.   -  person Jeroen Mostert    schedule 18.11.2014
comment
@JeroenMostert да, я думал пойти по этому пути, чтобы быть в безопасности. Просто выглядит не очень красиво. Если вы поместите свой последний комментарий в качестве ответа, я приму его.   -  person pleinolijf    schedule 19.11.2014
comment
Я бы с удовольствием, но я не могу, потому что, если вы вот так поставите меня на линию, мне придется покопаться в этом, чтобы понять, что я был неправ. Извините за это - я надеюсь, что ответ компенсирует это. :-)   -  person Jeroen Mostert    schedule 19.11.2014
comment
Есть ли гарантия, что возвращаемое q.Receive() сообщение совпадает с сообщением, которое было бы возвращено q.EndPeek(e.asyncResult)? Интересно, что при настройке нескольких прослушивателей это приведет к потере сообщений или к тому, что сообщения останутся в исходной очереди.   -  person Micaël Félix    schedule 22.09.2016


Ответы (1)


Чтобы дать правильный ответ на этот вопрос, нужно приложить некоторые усилия, потому что короткий ответ («нет») может ввести в заблуждение.

В описании API явно говорится, что вы должны вызывать EndPeek() для каждого вызова BeginPeek()? Ни в одной теме, которую я смог найти, и не только в ней, похоже, утверждается обратное здесь :

Чтобы использовать BeginPeek, создайте обработчик событий, который обрабатывает результаты асинхронной операции, и свяжите его с вашим делегатом события. BeginPeek инициирует асинхронную операцию просмотра; MessageQueue уведомляется посредством создания события PeekCompleted, когда сообщение поступает в очередь. Затем MessageQueue может получить доступ к сообщению, вызвав EndPeek(IAsyncResult) или, получив результат с помощью метода PeekCompletedEventArgs.

(Выделение мое.) Кажется, это говорит о том, что вы можете либо использовать .EndPeek(), либо просто напрямую получить сообщение из аргументов события без необходимости вызывать .EndPeek().

Хорошо, значит, реализация требует, чтобы вы вызывали .EndPeek(), чтобы все работало правильно? По крайней мере, для реализации System.Messaging в .NET 4.0 ответ отрицательный. При вызове .BeginPeek() выделяется асинхронная операция и регистрируется обратный вызов для завершения. Неуправляемые ресурсы, связанные с этой операцией, частично очищаются в этом обратном вызове, и только затем вызывается обработчик события. .EndPeek() на самом деле не выполняет никакой очистки — он просто ожидает завершения операции, если она еще не завершена, проверяет наличие ошибок и возвращает сообщение. Так что это действительно правда, что вы можете либо вызвать .EndPeek(), либо просто получить доступ к сообщению из аргументов события, либо вообще ничего не делать - все это будет работать так же плохо.

Плохо, да — заметьте, я сказал «частично очищен». Реализация MessageQueue имеет проблему в том, что она выделяет ManualResetEvent для каждой асинхронной операции, но никогда не удаляет ее, оставляя это полностью на усмотрение сборщика мусора — разработчики .NET часто подвергаются резкой критике за то, что они делают, но, конечно, собственные разработчики Microsoft не тоже не идеально. Я не проверял, актуальна ли утечка OverlappedData, описанная в этом вопросе, и это сразу видно из первоисточника, но меня бы это не удивило.

У API есть и другие предупреждающие признаки того, что его реализация может оставлять желать лучшего, в первую очередь то, что он не следует установленному шаблону .Begin...()/.End...() для асинхронных операций, а вводит обработчики событий посередине, создавая странный гибрид, которого я никогда нигде не видел. еще. Затем есть очень сомнительное решение сделать класс Message наследником Component, что значительно увеличивает нагрузку на каждый экземпляр и поднимает вопрос о том, когда и как его следует удалять... в общем, не лучшая работа Microsoft.

Значит ли это, что вы не «обязаны» звонить .EndPeek()? Да, в том смысле, что вызов или не вызов не имеет функционального значения в отношении очистки ресурсов или корректности. Но несмотря на все вышесказанное, я все равно советую назвать его в любом случае. Почему? Потому что любой, кто знаком с тем, как работает шаблон асинхронной операции в других классах .NET, будет ожидать, что вызов будет там, и его отсутствие выглядит как ошибка, которая может привести к утечке ресурсов. Если есть проблема с приложением, такой человек может разумно потратить некоторые бесплодные усилия на поиск «проблемного кода», который не является таковым. Учитывая, что вызов .EndPeek() имеет незначительные накладные расходы по сравнению с остальным механизмом, я бы сказал, что экономия на программистах больше удивляет, чем компенсирует затраты. Возможная альтернатива состоит в том, чтобы вместо этого вставить комментарий, объясняющий, почему вы не вызываете .EndPeek(), но, по всей вероятности, для понимания этого потребуется больше программных циклов, чем просто вызов.

Теоретически еще одна причина для его вызова заключается в том, что семантика API может измениться в будущем, чтобы сделать вызов .EndPeek() необходимым; на практике это очень маловероятно, потому что Microsoft традиционно неохотно вносит подобные критические изменения (код, который ранее и разумно не вызывал .EndPeek(), перестанет работать), а существующая реализация уже противоречит установившейся практике.

person Jeroen Mostert    schedule 19.11.2014