TcpClient против Socket при работе с асинхронностью

Это не еще один TcpClient vs Socket.

TcpClient - это оболочка вокруг класса Socket для облегчения разработки, а также доступ к базовому Socket.

еще ...

На странице библиотеки MSDN для класса TcpClient можно прочитать следующее замечание:

Класс TcpClient предоставляет простые методы для подключения, отправки и получения потоковых данных по сети в синхронном режиме блокировки.

А для класса Socket:

Класс Socket позволяет выполнять как синхронную, так и асинхронную передачу данных с использованием любого из протоколов связи, перечисленных в перечислении ProtocolType.

Чтобы отправлять / получать некоторые данные асинхронно только через TcpCient, необходимо вызвать GetStream, чтобы получить базовый NetworkStream, из / на котором данные могут быть прочитаны / записаны асинхронно, вызывая методы ReadAsync и WriteAsync на нем, следуя шаблону TAP (возможно использование конструкций async / await).

Чтобы отправлять / получать данные асинхронно через Socket (я не эксперт, но думаю, что понял правильно), мы можем напрямую читать / писать из / в самом экземпляре сокета, вызывая BeginRead / EndRead BeginWrite / EndWrite (или просто ReadAsync или WriteAsync .. не раскрывает шаблон TAP - т.е. не возвращает задачу .. сбивает с толку).

Прежде всего, есть идеи, почему класс Socket в .NET 4.5 никоим образом не реализует шаблон TAP, то есть ReadAsync и WriteAsync, возвращающие Task (событие, если вызывается по-другому, чтобы сохранить обратную совместимость)?

В любом случае, достаточно просто создать метод Task из пары методов модели APM, поэтому скажем, я вызываю этот асинхронный метод (для чтения) ReadAsyncTAP (возвращающий задачу).

Ok ? Итак, теперь предположим, что я хочу закодировать клиентский метод async Task<Byte[]> ReadNbBytes(int nbBytes), который я буду вызывать из своего кода для асинхронного чтения определенного количества байтов из сети.

Реализация этого метода, основанная исключительно на TcpClient, получит NetworkStream путем вызова GetStream и будет содержать асинхронный цикл, ожидающий вызова (ов) ReadAsync, пока буфер не заполнится.

Реализация этого метода на основе Socket будет содержать асинхронный цикл, ожидающий на ReadAsyncTAP, пока буфер не заполнится.

В конце концов, с точки зрения клиентского кода, я полагаю, это не имеет значения. В обоих случаях вызов await ReadNbBytes немедленно «вернется». Однако я полагаю, что это имеет значение за кулисами ... Для TcpClient, полагающегося на NetworkStream, блокирует ли чтение как-то или нет в любой момент по сравнению с прямым использованием сокета? Если нет, то замечание, сделанное для TcpClient, неверно, когда речь идет о синхронном режиме блокировки?

Был бы очень признателен, если бы кто-нибудь мог прояснить!

Спасибо.


person darkey    schedule 16.08.2012    source источник


Ответы (1)


Асинхронный ввод-вывод в TcpClient потоках не блокируется. Похоже, что документы MSDN неверны (вы можете проверить это в Reflector, выполнив вызовы асинхронного ввода-вывода NetworkStream).

Stream типы "интересны": по умолчанию базовый класс Stream реализует асинхронный ввод-вывод, блокируя поток пула потоков при синхронном вводе-выводе. Таким образом, вы никогда не захотите выполнять асинхронный ввод-вывод на чем-то вроде MemoryStream, который предоставляет только синхронные методы.

NetworkStream обеспечивает асинхронный ввод-вывод, поэтому асинхронный ввод-вывод на NetworkStream экземплярах фактически является асинхронным. Но это не всегда так: FileStream, в частности, обычно не асинхронный, но он есть, если вы правильно сконструируете экземпляр.

Относительно того, почему Socket не имеет методов TAP: это очень хороший вопрос! Я предполагал, что это был недосмотр, но теперь, когда выпущен .NET 4.5, похоже, что это было сделано специально. Возможно, они просто не хотят усложнять API - Socket уже имеет синхронный и два асинхронных API, охватывающих один и тот же набор операций (Send, SendTo, Receive, ReceiveFrom, Connect, Accept, Disconnect ). TAP, в свою очередь, потребует два дополнительных асинхронных API для этого полного набора. Это, по крайней мере, вызовет интересную ситуацию с именами (имена *Async уже заняты, и они добавят еще два *Async имени для каждой операции).

Боковое примечание: «дополнительные» API предназначены для высокопроизводительной асинхронной Socket связи. Они используют SocketAsyncEventArgs, который не так прост в использовании, но производит меньше мусора в памяти. Если бы API TAP были добавлены в Socket, они хотели бы предоставить как простые в использовании версии (упаковка _22 _ / _ 23_), так и версии с более высокой производительностью (упаковка Async).

Если вам интересно создавать методы TAP для Socket, хорошей отправной точкой является Awaiting Socket Operations (он предоставляет только оболочки для высокопроизводительного API). Я использую нечто похожее для своих сокетов с async.

person Stephen Cleary    schedule 16.08.2012
comment
Мне понравилось решение Stepgen Toub, но я думаю, что вы могли бы написать что-то подобное. :) - person Şafak Gür; 16.08.2012
comment
Аналогичная ситуация с именованием имеет место с WebClient, где имена *Async используются для методов EAP (асинхронный шаблон на основе событий). Они решили эту проблему и назвали методы TAP *TaskAsync. - person svick; 16.08.2012
comment
@ d4wn: Работаем над этим. :) Из-за моей дневной работы безумно-бизнесом, наверное, не будет до 2013 года. :( - person Stephen Cleary; 16.08.2012
comment
@svick: Ситуация с именами в Socket сложнее. С этим одним классом из всего BCL они решили использовать *Async для обозначения использования SocketAsyncEventArgs вместо EAP. Таким образом, вы можете использовать *TaskAsync для оболочек в стиле APM, а затем, если вы будете следовать существующим соглашениям TAP и Socket, вы получите *AsyncAsync для оболочек в стиле SocketAsyncEventArgs. Уродливые и, безусловно, запутанные методы для и без того перегруженного типа. - person Stephen Cleary; 16.08.2012
comment
Еще раз ... Спасибо, Стивен! ;) - person darkey; 16.08.2012