TcpClient срещу Socket при работа с асинхронност

Това не е още едно TcpClient срещу 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 връщащи задача (събитие, ако се извика по различен начин, за да се запази обратната съвместимост)?

Както и да е, достатъчно лесно е да се изгради метод на задача от двойка методи на APM модел, така че да кажем, че наричам този асинхронен метод (за четене) ReadAsyncTAP (връщане на задача).

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

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

Изпълнението на този метод, базиран на Socket, ще съдържа асинхронен цикъл, чакащ ReadAsyncTAP, докато буферът се запълни.

В крайна сметка, от гледна точка на клиентския код, предполагам, че няма значение. И в двата случая обаждането до await ReadNbBytes ще се „върне“ незабавно. Предполагам обаче, че има разлика зад кулисите ... За TcpClient, разчитащ на NetworkStream, блокира ли четенето по някакъв начин или не в даден момент, в сравнение с директното използване на socket? Ако не, грешна ли е забележката за TcpClient, когато говорим за режим на синхронно блокиране?

Ще се радвам много, ако някой може да изясни!

Благодаря.


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


Отговори (1)


Асинхронният I/O на TcpClient потоци не блокира. Изглежда, че документите на MSDN са грешни (можете да проверите това в Reflector, като следвате асинхронните I/O извиквания на NetworkStream).

Типовете Stream са "интересни": по подразбиране базовият клас Stream ще реализира асинхронен I/O, като блокира нишка от пул от нишки при синхронен I/O. Така че никога не искате да правите асинхронен I/O на нещо като MemoryStream, което предоставя само синхронни методи.

NetworkStream осигурява асинхронен I/O, така че асинхронният I/O на NetworkStream екземпляри всъщност е асинхронен. Но това не винаги е така: FileStream по-специално е обикновено не асинхронно, но е, ако конструирате екземпляра точно.

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

Странична бележка: „допълнителните“ API са за високопроизводителна асинхронна Socket комуникация. Те използват SocketAsyncEventArgs, което не е толкова лесно за използване, но генерира по-малко боклук в паметта. Ако TAP API бяха добавени към Socket, те биха искали да предоставят както лесните за използване версии (обвиване Begin/End), така и версиите с по-висока производителност (обвиване Async).

Ако се интересувате от създаването на TAP методи за Socket, добра отправна точка е Изчакване на операции със сокет (той предоставя само обвивки за високопроизводителния 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