Diskuze: TcpClient.Read - čekání na odpověď bez zaseknutí hlavního vlákna
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Člen
Zobrazeno 37 zpráv z 37.
//= Settings::TRACKING_CODE_B ?> //= Settings::TRACKING_CODE ?>
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
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
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
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.
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í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?
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.
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í?
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...
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?
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?
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.
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
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
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
Právě komponenta BackgroundWorker řeší synchronizaci vláken automaticky, bez nutnosti nějakých složitých nastavování...
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š.
Takže BackgroundWorker jako nové vlákno ideální pro amatéra jako já abych se nemusel starat o thread safe proměnné?
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
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"...
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?
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ě...
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
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?
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.
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
.
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
Tak ještě jeden dotaz,
K čemu tam přesně slouží ten object u toho locku?
private Object thisLock = new Object();
lock (thisLock)
{
}
K identifikaci zámku, těch zámků může bejt najednou aktivních víc.
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?
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.
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ě?
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.
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í?
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 !
Zobrazeno 37 zpráv z 37.