Как корректно остановить асинхронное чтение из 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
От: merk Россия  
Дата: 23.07.08 11:23
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Добрый день!


А>Пишу класс 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
От: merk Россия  
Дата: 23.07.08 11:37
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Добрый день!


общий подход к разработке колбеков.
1. колбек должен иметь по возможности неблокируемый код, или с минимальным по времени блокированием.
2. код должен быть минимальным и представляющим собой например переброс данных, полученных в этом колбеке в виде сообщения какому-то регулярному вашему треду, или заталкивание их в очередь.
3. ставить и снимать колбэк можно только извне. с правильной синхронизацией при снятии, если в момент снятия колбек еще работает. в принципе это долна обеспечивать ваша библиотека, что дает вам возможность вешать колбеки. что происходит при снятии колбека, когда он исполняется — это место нужно посмотреть в доках, там это должно как-то описываться поскольку это класический случай для колбеков.
4. реентер в колбек обычно означает, что колбек слишком тяжел и пока выполняется предыдущий вход, уже пошел новый вход. это очень плохая ситауция и должна контролироваться. также может быть ситуация, что несмотря на ваш очень быстрый код колбеке, поток данных настолько велик, что вы захлебываетесь. тут уже вопрос в адекватности аппаратуры вашей задаче.
Re[2]: Как корректно остановить асинхронное чтение из UdpCli
От: merk Россия  
Дата: 23.07.08 11:42
Оценка:
Здравствуйте, 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 Россия  
Дата: 23.07.08 13:46
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Здравствуйте, merk, Вы писали:


>> колбеку нежелательно управлять самим собой.

А>Я собираюсь попробовать следующую схему. DataReceiver.Start запускает поток, который крутится в цикле, запуская асинхронную операцию получения дейтаграммы и ожидая ее завершения или сигнала к остановке. Callback вообще не используется.
если там есть и такой безколбековый способ, то он вполне правомерен. скорее всего это делается через скрытый колбек, врапированный. но эсли это проще, то чем проще тем лучше.

>> еще хуже блокировки в колбеке за счет вызова каких то ваших "передач данных подписчикам"

А>С этим как раз проблем нет — я генерирую event, подписчик которого добавляет пакте в очередь (producer / consumer), задержка минимальна. То, что подписчик обязан обеспечить минимальную задержку, документировано в интерфейсе класса.
это правильно. но я в таких случах, также проверяю таки, что подписчик обеспечивает этот контракт. что делается проверкой на реентер. в реальности может быть ситуация что подписчик контракт хотел бы обеспечить, но не может. например пошла сборка мусора, если это .net, или очередь eventoв у вас переполняется. тогда если не проверять система просто застрянет. а если проверять — будет ругаться в лог, что подписчик тормозит и все такое.

А>Спасибо за ответ. Подумав над вопросами и перечитав Asynchronous Programming Design Patterns, решил отказаться от callback. Асинхронность по-прежнему нужна, чтобы можно было завершить работу DataReceiver во время ожидания очередного пакета (или если пакетов вообще нет).

асинхронность нужна по любому, поскольку данные к вам приходят вне засимости от вашего желания. а принципиальные вещи не устранишь никакими трюками.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.