Как корректно остановить асинхронное чтение из UdpClient?
От:
Аноним
Дата:
23.07.08 08:31
Оценка:
Добрый день!
Пишу класс DataReceiver, получающий UDP пакеты и передающий их в очередь для дальнейшей обработки. Получение реализовано с использованием асинхронных методов BeginReceive, EndReceive класса UdpClient.
public class DataReceiver : IDisposable
{
private UdpClient _client;
private bool _disposed;
public void Start()
{
_client.BeginReceive(ReceiveCallback, null);
}
public void Stop()
{
_client.Close();
}
private void ReceiveCallback(IAsyncResult ar)
{
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
try
{
byte[] data = _client.EndReceive(ar, ref remoteEP);
// Передача на обработку подписчику ...
_client.BeginReceive(ReceiveCallback, null);
}
catch (ObjectDisposedException)
{ /* Stopped? */ }
catch (SocketException)
{ /* Stopped? */ }
}
/* IDisposable Members implementation */
}
Корректна ли данная реализация? Как правильно остановить получение данных?
В приведенном коде меня смущает необходимость перехватывать ObjectDisposedException и SocketException внутри ReceiveCallback. Эти исключения возникают, если ReceiveCallback срабатывает в результате или после вызова _client.Close. Поэтому в принципе, видится нормальным их перехват: DataReceiver полагает, что остановлен и не делает новых запросов на получение пакетов. Однако SocketException может возникнуть и "штатно", DataReceiver в этом случае поведет себя неправильно: прекратит дальнейшее получение пакетов.
Фактически, здесь проблема использования ресурса из разных потоков. UdpClient используется из основного потока, вызывающего Start, Stop, и потоками из пула, в контексте которых срабатывает ReceiveCallback. Возможно, стоит ввести флаг "остановлен" и синхронизировать доступ к нему и UdpClient? А как быть с блокировкой, которая появится в реализации Dispose?
Re: Как корректно остановить асинхронное чтение из UdpClient
Здравствуйте, Аноним, Вы писали:
А>Добрый день!
А>Пишу класс DataReceiver, получающий UDP пакеты и передающий их в очередь для дальнейшей обработки. Получение реализовано с использованием асинхронных методов BeginReceive, EndReceive класса UdpClient.
чиста теоретические рассуждения, поскольку деталей приема udp пакетов не знаю.
буду писать некие общие замечания.
А>
А> public class DataReceiver : IDisposable
А> {
А> private UdpClient _client;
А> private bool _disposed;
А> public void Start()
А> {
А> _client.BeginReceive(ReceiveCallback, null);
А> }
А> public void Stop()
А> {
А> _client.Close();
А> }
если в старте вы начали прием, то в стопе долна быть явная функция снимающая колбек. если close это обеспечивает, то и хорошо. если нет - нужно писать видимо EndReceive явно прямо тут. только возникнут вопросы синхронизации. если в данный момент будет исполняться колбек могут быть проблемы. надо посмотреть доки как правильно снимать колбеки. поскольку случай существенно получается мультиредовый(колбек вызывается тредом обслуживающим их, а не текущим).
А> private void ReceiveCallback(IAsyncResult ar)
А> {
А> IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
нельзя ли как то избавиться от new в колбеке? колбек должен быь минимальной функцией обеспечивающей вашу "идею". его задача взять данные и куда-то быстро перепихнуть вашим тредам. и уж там вы будете с этими данными разбираться.
А> try
А> {
А> byte[] data = _client.EndReceive(ar, ref remoteEP);
тут вопрос. это колбек дезактвируется и пока не скажут стартресив - вызываться не будет?
это плохо. колбеку нежелательно управлять самим собой. обычно колбэк должен взводить булеву переменную на вхлоде - нахожусь_внутри_колбека, и проверять ее перед этим. при выходе - ставить переменную в false, таким образом контролируется повторный вход в колбек. повторный вход в колбек служит признаком того что вы захлебываетесь в обработке. нужно принимать какие-то меры.
А> // Передача на обработку подписчику ...
А> _client.BeginReceive(ReceiveCallback, null);
А> }
А> catch (ObjectDisposedException)
А> { /* Stopped? */ }
А> catch (SocketException)
А> { /* Stopped? */ }
в колбеке, поскольку он вызывается неким не вашим тредом, я думаю разумно перехватить все эксепшены. а лучше сократить его функциональность до достаточно элементарных операции, чтобы самих эксепшенов было поменьше.
А> }
А> /* IDisposable Members implementation */
А> }
А>
А>Корректна ли данная реализация? Как правильно остановить получение данных?
правильно остановить снятием колбека не из него самого.
А>В приведенном коде меня смущает необходимость перехватывать ObjectDisposedException и SocketException внутри ReceiveCallback. Эти исключения возникают, если ReceiveCallback срабатывает в результате или после вызова _client.Close. Поэтому в принципе, видится нормальным их перехват: DataReceiver полагает, что остановлен и не делает новых запросов на получение пакетов. Однако SocketException может возникнуть и "штатно", DataReceiver в этом случае поведет себя неправильно: прекратит дальнейшее получение пакетов.
вот потому то перед client.close должен явно снять колбек, но аккуратно(см выше).
А>Фактически, здесь проблема использования ресурса из разных потоков. UdpClient используется из основного потока, вызывающего Start, Stop, и потоками из пула, в контексте которых срабатывает ReceiveCallback. Возможно, стоит ввести флаг "остановлен" и синхронизировать доступ к нему и UdpClient? А как быть с блокировкой, которая появится в реализации Dispose?
никак не быть с блокировкой. если endRecieve , блокирующая операция до момента физического снятия колбека — то это естественная блокировка.
еще хуже блокировки в колбеке за счет вызова каких то ваших "передач данных подписчикам". именно в колбеке важно не иметь блокировок, способных затянуть во времени исполнение колбека.
А>
Re: Как корректно остановить асинхронное чтение из UdpClient
общий подход к разработке колбеков.
1. колбек должен иметь по возможности неблокируемый код, или с минимальным по времени блокированием.
2. код должен быть минимальным и представляющим собой например переброс данных, полученных в этом колбеке в виде сообщения какому-то регулярному вашему треду, или заталкивание их в очередь.
3. ставить и снимать колбэк можно только извне. с правильной синхронизацией при снятии, если в момент снятия колбек еще работает. в принципе это долна обеспечивать ваша библиотека, что дает вам возможность вешать колбеки. что происходит при снятии колбека, когда он исполняется — это место нужно посмотреть в доках, там это должно как-то описываться поскольку это класический случай для колбеков.
4. реентер в колбек обычно означает, что колбек слишком тяжел и пока выполняется предыдущий вход, уже пошел новый вход. это очень плохая ситауция и должна контролироваться. также может быть ситуация, что несмотря на ваш очень быстрый код колбеке, поток данных настолько велик, что вы захлебываетесь. тут уже вопрос в адекватности аппаратуры вашей задаче.
Re[2]: Как корректно остановить асинхронное чтение из UdpCli
Здравствуйте, merk, Вы писали:
M>Здравствуйте, Аноним, Вы писали:
А>>Добрый день!
M>общий подход к разработке колбеков. M>4. реентер в колбек обычно означает, что колбек слишком тяжел и пока выполняется предыдущий вход, уже пошел новый вход. это очень плохая ситауция и должна контролироваться. также может быть ситуация, что несмотря на ваш очень быстрый код колбеке, поток данных настолько велик, что вы захлебываетесь. тут уже вопрос в адекватности аппаратуры вашей задаче.
5. нежелательно чтобы из колбека исключения, если они могут быть, вообще выходили. если только это не отладочная ситуация. их желательно перехватить в колбеке, и например взвести какую-то внешнюю переменную типа — ошибка_в_колбеке в true. наличие такой ошибки является серьезным признаком проблем с проектированием вашей системы.
Re[2]: Как корректно остановить асинхронное чтение из UdpCli
От:
Аноним
Дата:
23.07.08 13:26
Оценка:
Здравствуйте, merk, Вы писали:
> если в старте вы начали прием, то в стопе долна быть явная функция снимающая колбек
К сожалению, мне не известен способ отменить запущенную асинхронную операцию.
> нельзя ли как то избавиться от new в колбеке
Да, сделаю remoteEP членом класса, чтобы не создавать каждый раз новый экземпляр.
> тут вопрос. это колбек дезактвируется и пока не скажут стартресив — вызываться не будет? > это плохо.
Да, callback срабатывает один раз, когда асинхронная операция выполнена. Для получения результата асинхронной операции требуется вызвать EndReceive.
> колбеку нежелательно управлять самим собой.
Я собираюсь попробовать следующую схему. DataReceiver.Start запускает поток, который крутится в цикле, запуская асинхронную операцию получения дейтаграммы и ожидая ее завершения или сигнала к остановке. Callback вообще не используется.
> еще хуже блокировки в колбеке за счет вызова каких то ваших "передач данных подписчикам"
С этим как раз проблем нет — я генерирую event, подписчик которого добавляет пакте в очередь (producer / consumer), задержка минимальна. То, что подписчик обязан обеспечить минимальную задержку, документировано в интерфейсе класса.
Спасибо за ответ. Подумав над вопросами и перечитав Asynchronous Programming Design Patterns, решил отказаться от callback. Асинхронность по-прежнему нужна, чтобы можно было завершить работу DataReceiver во время ожидания очередного пакета (или если пакетов вообще нет).
Re[3]: Как корректно остановить асинхронное чтение из UdpCli
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, merk, Вы писали:
>> колбеку нежелательно управлять самим собой. А>Я собираюсь попробовать следующую схему. DataReceiver.Start запускает поток, который крутится в цикле, запуская асинхронную операцию получения дейтаграммы и ожидая ее завершения или сигнала к остановке. Callback вообще не используется.
если там есть и такой безколбековый способ, то он вполне правомерен. скорее всего это делается через скрытый колбек, врапированный. но эсли это проще, то чем проще тем лучше.
>> еще хуже блокировки в колбеке за счет вызова каких то ваших "передач данных подписчикам" А>С этим как раз проблем нет — я генерирую event, подписчик которого добавляет пакте в очередь (producer / consumer), задержка минимальна. То, что подписчик обязан обеспечить минимальную задержку, документировано в интерфейсе класса.
это правильно. но я в таких случах, также проверяю таки, что подписчик обеспечивает этот контракт. что делается проверкой на реентер. в реальности может быть ситуация что подписчик контракт хотел бы обеспечить, но не может. например пошла сборка мусора, если это .net, или очередь eventoв у вас переполняется. тогда если не проверять система просто застрянет. а если проверять — будет ругаться в лог, что подписчик тормозит и все такое.
А>Спасибо за ответ. Подумав над вопросами и перечитав Asynchronous Programming Design Patterns, решил отказаться от callback. Асинхронность по-прежнему нужна, чтобы можно было завершить работу DataReceiver во время ожидания очередного пакета (или если пакетов вообще нет).
асинхронность нужна по любому, поскольку данные к вам приходят вне засимости от вашего желания. а принципиальные вещи не устранишь никакими трюками.