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