Здравствуйте, Аноним, Вы писали:
А>Добрый день!
А>Пишу класс 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 , блокирующая операция до момента физического снятия колбека — то это естественная блокировка.
еще хуже блокировки в колбеке за счет вызова каких то ваших "передач данных подписчикам". именно в колбеке важно не иметь блокировок, способных затянуть во времени исполнение колбека.
А>