Обязан ли я вызывать 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), который в равной степени извлекает мне сообщение, но не удаляет сообщение из очереди.

1 ответ

Решение

Правильный ответ на этот вопрос требует определенных усилий, потому что короткий ответ ("нет") может вводить в заблуждение.

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

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

(Акцент мой.) Это говорит о том, что вы можете использовать .EndPeek() или просто получить сообщение от args события без обязательства звонить .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() перестанет работать), а существующая реализация уже противоречит сложившейся практике.

Другие вопросы по тегам