Diskuze: TcpClient.Read - čekání na odpověď bez zaseknutí hlavního vlákna

C# .NET .NET (C# a Visual Basic) TcpClient.Read - čekání na odpověď bez zaseknutí hlavního vlákna American English version English version

Avatar
Elisse
Člen
Avatar
Elisse:

Zdravím, jak zní nadpis...

TcpClient Client = new TcpClient("127.0.0.1", 13000);
NetworkStream Stream = Client.GetStream();
NetworkStream.Write(BufferWithHeader, 0, BufferWithHeader.Length);

a následně očekávám odpověď:

NetworkStream.Read(DataLengthInByte, 0, 4);

jaké jsou způsoby abych dosílil toho, že jakmile dojdou data co očekávám bude program pokračovat za read, nicméně mezitím nebude zaseknuté hlavní vlákno?

Četl jsem něco o ReadAsync, ale jsem nováček a vůbec tomu Async nerozumím, neexistuje jednodušší způsob? klidně ne tak efektivní :)

 
Odpovědět 30. listopadu 13:43
Avatar
Odpovídá na Elisse
Michal Štěpánek:

zkus se mrknout na BackgroundWorker

Nahoru Odpovědět 30. listopadu 14:05
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Elisse
Člen
Avatar
Odpovídá na Michal Štěpánek
Elisse:

BackgroundWorker, Task, Thread, vše funguje prakticky na stejno v mém případě, ne?

Můžu tenhle zápis a čtení pustit na jiném vlákně, ale problém nastává pak v komunikaci s tím hlavním vláknem. Bych musel vytvořit nějaký buffer, kam se budou obě vlákna dívat po zprávách?

Už nebudu moc použít jednoduchý return z metody

 
Nahoru Odpovědět 30. listopadu 14:14
Avatar
Elisse
Člen
Avatar
Odpovídá na Michal Štěpánek
Elisse:

Pro úplnost uvedu konkrétní příklad:

Mám metodu:

public byte[] Login(string Username, string Password)
{
    byte[] ByteUsername = Encoding.UTF8.GetBytes(Username);
    byte[] BytePassword = Encoding.UTF8.GetBytes(Password);

    DataForSend = AppendByteArray(ByteUsername ,BytePassword)

    Stream.Write(DataForSend, 0, DataForSend.Length);

    byte[] Data = new byte[50];
    Stream.Read(Data, 0, Data.Length);

    return Data
}

Nicméně teď to funguje tak, že se celý client zamrazí dokud ty data nedostane že jo, to je jasné.

A to potřebuju předělat nicméně stále potřebuji ty Data

Editováno 30. listopadu 14:23
 
Nahoru Odpovědět 30. listopadu 14:21
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Obvykle máš nějaký thread-safe List zpráv, do kterýho ti ty vlákna obsluhující TCP komunikaci jen házej přicházející zprávy a ty si je pak zpracováváš kde potřebuješ - typicky třeba i v tom hlavním vlákně.

Odesílání se pak obvykle řeší stejně - do seznamu naházíš zprávy k odeslání a vlákno obsluhující komunikaci se tam kouká a sype ty zprávy do TCP.

Akceptované řešení
+20 Zkušeností
+1 bodů
Řešení problému
Nahoru Odpovědět  +1 30. listopadu 14:47
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Bezva přesně todle řešení s tím bufferem na zprávy pro přístup z více vláken mě napadlo už dřív a nechtělo se mi to dělat, tak teďka za to zaplatím přepisováním zase :D

Díky moc! :)

Takže si udělám thread safe buffer a pak například:

pošlu request na server, nastavím si nějaký flag že čekám odpověď, když bude flag true tak to po ní budu v hlavním vlákně koukat do příchozího buferu, jakmile dojde, tak zruším flag, ano? :)

 
Nahoru Odpovědět 30. listopadu 14:52
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Ne.
Posíláš normálně jednotlivý zprávy, žádnej flag neřešíš.

To komunikační vlákno jen načte bajty a zrekonstruuje z nich zprávu, kterou máš reprezentovanou nějakou třídou a tu zprávu pak hodí do toho seznamu.

Editováno 30. listopadu 15:31
Nahoru Odpovědět 30. listopadu 15:31
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Já měl na mysli, že hlavní vlákno, které poslalo nějakou zprávu si musí někde nastavit, že má očekávat v brzké době odpověď aneb. že se při každém projetí mainloopu podívá jestli se mu už vrátila odpověď do toho seznamu nebo ne. Nebo je to chybné myšlení? :-O

Editováno 30. listopadu 15:40
 
Nahoru Odpovědět 30. listopadu 15:39
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Ne, proč bys něco takovýho dělal? :)

Prostě ti běží hlavní a komunikační vlákno, to komunikační ti hází jednotlivý zprávy do hlavního, ty tam na ně zareaguješ a hodíš to zpátky komunikačnímu.

A zprávy jsou samostatný věci, jako např. tohle je moje pozice, teď jsem vystřelil, teď chci obchodovat a odpovídáš mu zprávou tohle je pozice nepřátel, tohle jsi zasáhl, tohle je obsah obchodu... :)

Nahoru Odpovědět 30. listopadu 16:27
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

No todle je mi jasné, nicméně narážím na to, že odpověď daného typu by měl hledat jen když se na ni předtím ptal, ne?

Mám takový pocit, že já narážím na postup kdy se Hlavní vlákno kouká do bufferu jestli už mu přišla odpověď na něco na co se ptalo v každém cyklu

A ty na postup, že se při každém cyklu hlavního vlákna projede celý buffer? Chápeme se?

 
Nahoru Odpovědět 30. listopadu 16:43
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Zkrátka já myslel, že se bude hlavní vlákno koukat do bufferu jen když se mu to řekne, nicméně aby se tam podívalo vždy a zpracuje vše co tam najde je asi mnohem lepší, myslel jsi to tak? :)

 
Nahoru Odpovědět 30. listopadu 17:18
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Ne, vůbec.

Máš dvě vlákna, jedno pořád poslouchá a když je co k přečtení, tak přečte bajty, vytvoří z nich instanci nějaký třídy Message a pošle ji hlavnímu vláknu ke zpracování.

V tom hlavním vlákně se jen koukáš na ty už načtený zpávy a jen pošleš odpověď, pokud je to potřeba.

Nahoru Odpovědět 30. listopadu 17:56
:)
Avatar
lukasko.simon:

osobne by som mozno tento pripad riesil cez async/await, vlakna su dobra vec no volil by som to skor pri velkych enterprise projektoch kde je to fakt nevyhnutne, pretoze ladenie a synchronizacia vlakien to je smrt... Implementacia pomocou async/await je celkom jednoducha, lahsia na debug, ale to je iba moj nazor :) 100 ludi 100 chuti :)

Editováno 30. listopadu 22:24
 
Nahoru Odpovědět 30. listopadu 22:23
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Asi mám právě problém s tím pochopením "pošle hlavnímu vláknu ke zpracování", možná bych mohl zmínit že hlavní vlákno je prakticky while (true) smyčka

 
Nahoru Odpovědět 1. prosince 8:42
Avatar
Elisse
Člen
Avatar
Odpovídá na lukasko.simon
Elisse:

To už mi radilo víc lidí a nejspíš máte pravdu, nicméně mě jako začátečnikovi to moc jednoduché nepřišlo, když jsem se to snažil na msdn nebo někde pochopit :D

 
Nahoru Odpovědět 1. prosince 8:43
Avatar
Odpovídá na Elisse
Michal Štěpánek:

Právě komponenta BackgroundWorker řeší synchronizaci vláken automaticky, bez nutnosti nějakých složitých nastavování...

Nahoru Odpovědět 1. prosince 8:57
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Zkusím to na příkladu - chci třeba jít do obchodu a obchodovat:

Máš třídu reprezentující nějakou Message - třeba něco jako

VisitShopMessage : NetMessage
{
  int IdShop;
  int IdPlayer;
}

Pak máš nějakej thread-safe List<NetMessage> (nebo Queue - fronta) a nebo všechny přístupy k tomu listu/frontě třeba jen zamykáš přes lock
https://msdn.microsoft.com/…5kehkcz.aspx
a obě vlákna mají někde referenci na ten list.

Čtecí vlákno pořád běží a jen kouká, jestli má co číst, pokud ano, tak přečte bajty, udělá z nich instanci ty třídy VisitShopMessage a uloží do toho listu, nic víc neřeší.
Ještě případně doplní playerId podle Connection, aby nebylo možný z klienta nafejkovat, že tu zprávu poslal někdo jinej.

Hlavní vlákno se v každým loopu do toho seznamu koukne, projde všechny zprávy, zpracuje je (tady třeba bys zareagoval tak, že bys poslal zpátky hrářovi zprávu, ve který bude seznam zboží toho obchodu) a pak ty zprávy vymažeš.

Editováno 1. prosince 8:59
Nahoru Odpovědět 1. prosince 8:58
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Michal Štěpánek
Elisse:

Takže BackgroundWorker jako nové vlákno ideální pro amatéra jako já abych se nemusel starat o thread safe proměnné?

 
Nahoru Odpovědět  ±0 1. prosince 8:59
Avatar
Odpovídá na Michal Štěpánek
Luboš Běhounek (Satik):

To je trochu zavádějící vyjádření, pořád musíš řešit všechny synchronizační problémy a udržovat to thread-safe sám :)

Nahoru Odpovědět 1. prosince 9:00
:)
Avatar
Odpovídá na Elisse
Michal Štěpánek:

někde na internetu je na použití BW video, z kterého jsem to pochopil i já a to je co říct, bo mě se to musí vysvětlovat "polopaticky"...

Editováno 1. prosince 9:01
Nahoru Odpovědět 1. prosince 9:01
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

No jasně :)

To jsem zmínil předtím jestli to tak myslíš,

, nicméně aby se tam podívalo vždy a zpracuje vše co tam najde je asi mnohem lepší, myslel jsi to tak? :)

Jsem z toho jelen nebo jsi to přehlídl ty? :D

 
Nahoru Odpovědět 1. prosince 9:02
Avatar
Odpovídá na Luboš Běhounek (Satik)
Michal Štěpánek:

Ale oproti klasické práci s vlákny je toto "blbuvzdorné" a pro mě to bylo v podstatě jediné možné řešení..., protože práci s vlákny, resp. synchronizaci dat jsem dodnes nepochopil. Snažím se o to taaaak dlouho a taaak marně...

Nahoru Odpovědět 1. prosince 9:03
Nikdy neříkej nahlas, že to nejde. Vždycky se totiž najde blbec, který to neví a udělá to...
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Možná jsem špatně pochopil, co myslíš tím bufferem - já to pochopil jako buffer toho TCP připojení, kde máš ještě data v surový nezpracovaný podobě.

Pokud jsi tady tím bufferem myslel ten list/frontu, pak jsi to pochopil dobře :)

Nahoru Odpovědět 1. prosince 9:05
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Použil jsem špatnou terminologii no, bufferem jsem měl na mysli ten List :)

Nicméně budu moc rád, když se ještě zastavíme nad tím problémem s vlákny, koukl jsem na něco ohledně toho thread safe listu a když říkáš, že samotný BW to neřeší, tak jsem koukl z rychla na něco ohledně tech zámků.

Stačí tedy teoreticky, aby se pro nějaký Read a Write toho Listu přímo do těch metod zaimplementovali ty locky nebo je za tím mnohem víc?

 
Nahoru Odpovědět 1. prosince 9:09
Avatar
Odpovídá na Michal Štěpánek
Luboš Běhounek (Satik):

Ne, BW jen obyčejným vláknům přidává pár událostí a udržuje si nějaký threadpool - takže nemusí pokaždý znova vytvářet nový vlákno (celkem drahá operace), ale použít nějaký starý, který už je volný.
Ale zase tuším neumí nastavovat vláknům, jestli jsou background - to ovlivňuje, kdy se aplikace zavře a kdy zůstane běžet, když ještě nějaký vlákno zůstane běžet.

Takže se obecně doporučuje BW používat na nějaký kratší operace, kterých je hodně - třeba stahování souborů v downloadmanageru apod., na vlákno, co běží celou dobu a čte z TCP připojení nějaký data ve hře bych to spíš nepoužil.

Jinak musíš dávat stejnej pozor jako když Thread použiješ přímo.

Nahoru Odpovědět  +1 1. prosince 9:11
:)
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Pro začátek stačí všechny přístupy (čtení/zápis) do toho listu zalockovat.
Není to zrovna rychlá operace, ale pro začátek to neřeš, časem můžeš sehnat thread-safe frontu a nebo si to napsat sám, třeba přes Interlocked :) .

Nahoru Odpovědět 1. prosince 9:20
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Okay, bezva díky moc! Hádám, že brzo narazím na něco dalšího tak tu asi bude bez tak za chviličku nové téma na fóru :D

 
Nahoru Odpovědět 1. prosince 9:28
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Tak ještě jeden dotaz,

K čemu tam přesně slouží ten object u toho locku?

private Object thisLock = new Object();

lock (thisLock)
        {
        }
 
Nahoru Odpovědět 1. prosince 9:38
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

K identifikaci zámku, těch zámků může bejt najednou aktivních víc.

Viz https://msdn.microsoft.com/…5kehkcz.aspx :)

Editováno 1. prosince 9:58
Nahoru Odpovědět 1. prosince 9:57
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

No odtud jsem to zkopíroval a nebylo mi to jasné.

Takže například kdybych potřeboval více těchhle thread safe proměných ve stejné třídě?

Já použiji pro read i write ten stejný, ne? :)

 
Nahoru Odpovědět 1. prosince 10:02
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Ono to neudělá proměnnou thread-safe, jen kód uvnitř dvou zámků zamčených stejným objektem nemůže běžet najednou.

Pokud by k tomu mělo dojít, tak ten druhý (nebo i třetí, čtvrtý, ...) počká, až to ten předchozí uvolní.

Takže lock zamyká kód, ne proměnnou, tím objectem jen určuješ identifikátor toho zámku.

Nahoru Odpovědět 1. prosince 10:43
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Opět špatná terminologie pardon, z mého pohledu se to tak chová, že pomocí těch dvou metod čtení a zápis ji udělám prakticky thread safe z toho pohledu pokud budu používat jen ty dvě zalockované metody.

a tím identifikátorem hádám jde o to, že read i write z tohodle listu mají stejný identifikátor, kdybych vytvářel lock pro čtení nebo zápis na jinou proměnou můžu použít jiný identifikátor, správně? :)

Editováno 1. prosince 11:12
 
Nahoru Odpovědět 1. prosince 11:11
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

J, pokud si jseš jistej, že nikde jinde než v těch dvou místech se s k té proměnné přistupovat nebude, tak je to ok. Jen bacha, aby si někdo referenci na ten List třeba někde nezkopíroval.

Ano, když použiješ jiný id, tak ta sekce s jiným id bude nezávislá na těch s prvním id. :)

Nahoru Odpovědět 1. prosince 11:25
:)
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

List je private a ve finále to bude buildnuté DLL, tak by v tom snad neměl být problém :)

Ještě taková maličkost, hádám že do těch locků by se mělo dávat co nejmíň kódů aby to na co nejkratší dobu zbrzdilo ostatní vlákna, že?

Tzn. v případě nějakých náročnějších operací s tím Listem je možná lepší ho v tom locku jen zkopírovat do nové dočasné proměnné a pracovat s ní?

Editováno 1. prosince 11:34
 
Nahoru Odpovědět  +1 1. prosince 11:32
Avatar
Elisse
Člen
Avatar
Odpovídá na Luboš Běhounek (Satik)
Elisse:

Bezva, ještě jednou díky moc a hádám, že se brzy potkáme v dalším vlákně, pokud se ti bude chtít pomáhat ! :D

 
Nahoru Odpovědět 1. prosince 11:40
Avatar
Odpovídá na Elisse
Luboš Běhounek (Satik):

Pokud o něj zakopnu, tak klidně :)

Nahoru Odpovědět 1. prosince 11:58
:)
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 37 zpráv z 37.