Diskuze: Async TCP server - vyletí využití CPU 60% po odpojení clienta
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.
Člen
Zobrazeno 28 zpráv z 28.
//= 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.
Problém je ten, že když zavřeš spojení na klientovi, tak na serveru ti to při pokusu číst data vrací 0 Při čtení hlavičky máš podmínku bytes < 4, který je tedy pořád true. Takže se pořád dokola snažíš číst a nedostaneš se z while cyklu -> vysoké využití CPU.
Dále tam máš nepřímé rekurze (WaitForClientConnect->OnClientConnect a WaitForHeader->WaitForData->ExecuteMessage). Docela divný návrh. Pokud by server běžel dlouho a připojovalo se hodně lidí, tak by ti časem vyskočila vyjmka StackOverflowException-
Ti druzí mají s void a async pravdu. Protože tam máš hned na začátku pouze WaitForClientConnect, tak hned po zavolání metody se pokračuje dál, tj. nečeká se na připojení. Když změníš async void na async Task a při volání použiješ await, tak se bude čekat. Takže buď používej správně async/await nebo nepoužívej vůbec async metody.
Malý tip na konec. Zkus zvážit použití BinaryReaderu a BinaryWriteru, ať se nemusíš starat o práci s bytama.
Když bych nepoužíval async/await, tak asi vytvářím pro každého uživatele nový Task za což jsem dostal vyhubováno na jiném fórku ještě víc
Jinak k tomu prvnímu mám pocit, že když ten connection uzavřu, tak by to mělo skočit při pokusu o čtení do vyjímky, ne? Ale určitě to otestuju až se k tomu dostanu.
A s tou rekurzí si bohužel nevím rady jak ten návrh udělat jinak jsem bohužel amatér zatím
Tak jednoduše nepoužívej AcceptTcpClientAsync, ReadAsync a WriteAsync. Použij sync metody.
Při pokusu o čtení ti nevyskočí vyjímka. Viz. dokumentace
Vyskočí když je socket zavřený, právě otestováno
IOException
když nepoužiju async metody, tak nemám ponětí jak to udělat rozumně aby to dokázalo obsluhovat více klientů a do toho šlo zároveň psát do konzole.
Ha tak už vím jak to je, když klienta na tvrdo killnu, tak to vyhodí exception, ale když na klientovi zavolám TcpClient.close() tak ji to nevyhodí.
To čtení když vrací nulu si ošetřím to je tedy v pořádku, ale rád bych se vrátil celkově k tomu špatnému návrhu, zda by jsi mi ty nebo někdo mohl pomoci s tím jak to udělat do přijatelné varianty.
Jinak část kódu
private async void WaitForClientConnect()
{
TcpClient client = await server.AcceptTcpClientAsync();
Console.WriteLine("The async connection was created for: " + ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString());
OnClientConnect(client);
}
čeká dokud se někdo nepřipojí
Pokud nanapíšeše await před volání WaitForClientConnect(), tak opravdu nečeká. Měl bys to nastudovat.
A proč se tedy:
The async connection was created for...
vypíše až po tom co se někdo připojí?
Zkus přidat výpis do těchto metod:
public void ServerStart()
{
server.Start();
WaitForClientConnect();
Console.WriteLine(".....ServerStart");
}
private void OnClientConnect(TcpClient client)
{
ClientLowAPI clientReq = new ClientLowAPI(client);
WaitForClientConnect();
Console.WriteLine(".....OnClientConnect");
}
Uvidíš, že ten výpis bude dřív, než se někdo stihne připojit, tedy bude to před "The async connection created for....".
Vedlejší účinek tvého návrhu je to, že to vypadá, že to funguje
Omlouvám se o víkendu "nepracuju" a nejsem většinou ani na PC
No tak situace, kterou popisuješ bohužel nenastala viz. screenshot.
Každopádně pokud budeš mít čas a náladu budu velmi rád, když mi to pomůžeš dostat do rozumného stavu a pochopit ten async způsob, přepsat do to sync řešení jen protože to neumím bych nerad
No tak pokud vím, tak na awaitu se kód uspí a program pokračuje před async metodou a až se vrátí odpověď z awaitnuté metody tak se vyvolá na tom místě ne?
Kdybych to plně chápal z nějakého examplu, tak tu nezakládám thready na fórku Nejsem ten co se ptá ještě než napíše řádek kódu nebo googluje
Async/await nemá s uspáváním nic společného. Ať už je to c# nebo
JavaScript.
Představ si to tak, že když dáš před volání async funkce await, tak se
počká na její vyhodnocení a až pak se pokračuje sekvenčně dolů.
Ještě lepší představa by byla, kdybys náhodou znal a chápal koncept
promise. Async await je v podstatě sugar pro promisy.
Každopádně představovat si to jako uspání je špatně, takhle to
nefunguje.
Promiň špatná terminologie, uspí jsem myslel jako počká, nicméně to hlavní je ,ale to ,že pokračuje kód před tou async metodou, ne?
"Představ si to tak, že když dáš před volání async funkce await, tak se počká na její vyhodnocení a až pak se pokračuje sekvenčně dolů." - todle neni tak uplne 100% pravda. Na nic se nečeká. Právě, že kdyby se čekalo, tak je to k ničemu (a to hlavni vlakno ti vytuhne). Samozřejmě záleží hodně na configure await. (U STA i s await muze dispatch UI thread čekat, pokud je to špatně nastavené).Už z toho důvodu, to není stejné jako promise,..
Jen jsem se snažil přiblížit z nějaký běžný stránky věci. Vím, že to není takhle, ale vždycky jsem to tak viděl lidí vysvětlovat. Musím být přesnější, já vím.
Tomhle úplně nerozumím, jak může pokračovat něco před? To před, už se provedlo ne?
pseudo:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("1");
AsyncMethod();
Console.WriteLine("2");
}
public static async void AsyncMethod()
{
await MyMethod();
}
}
Výstup bude:
1
2
a po returnu z awaitnuté metody:
2
Takto jsem to myslel.
Tohle ale je úplně jinej případ.
Já mluvím o tomhle pseudokódu:
class program
main()
asyncFoo()
async asyncFoo()
print "první"
await asyncBar()
print "poslední"
async asyncBar()
print "druhý"
Protože tvůj kód sice awaituje, ale jako příklad použití je to zvláštní. Možná to tak potřebuješ, to nezpochybňuju.
A z tvého kódu nevidím, jestli se vypíše ještě jedna 2ka na konci.
Už se v tom ztrácím, uvedomu trošku v obraz co vlastně od svého kódu očekávám.
Pustím program.
Vytvoří se server, který poslouchá pro nové klienty asynchroně, protože potřebuji aby se tok programu dostal dále k ovládací konzoli serveru.
při vytvoření z vyvolaného returnu z await nové spojení, jakož to smyčku pro nového klienta, kde se už komunikuje tam a zpátky stále dokola.
Zkusím uvést obrázek, který snad ušetří mnoho otázek a nedorozumnění
Očividně nejde v úpravě příspěvku změnit obrázek, přidávám upravený (jedna šipka na víc)
No zkusil jsem to změnit a vytvořit něco takového? Je to lepší směr
nebo stále úplně špatně?
Hlavně jsem se chtěl zbavit té rekurze jak jsi říkal.
class ClientLowAPI
{
private TcpClient client;
private NetworkStream stream;
private string ip;
public ClientLowAPI(TcpClient clientConnected)
{
client = clientConnected;
stream = client.GetStream();
ip = string.Copy(((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString());
Listening();
}
private async void Listening()
{
while (true)
{
try
{
int length = await GetHeader();
byte[] message = await GetData(length);
byte[] asnwerMessage = ExecuteMessage(message);
SendMessage(asnwerMessage);
}
catch
{
stream.Close();
client.Close();
Console.WriteLine("The async connection was closed for: " + ip + " (Username: " + HighAPI.GetUsername() + ")");
return;
}
}
}
private async Task<int> GetHeader()
{
byte[] buffer = new byte[4];
int bytesRead = 0;
while (bytesRead < 4)
{
bytesRead += await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead);
if (bytesRead == 0)
{
throw new System.IO.IOException();
}
}
return FourBytesToInt(buffer);
}
private async Task<byte[]> GetData(int length)
{
byte[] buffer = new byte[length];
int bytesRead = 0;
while (bytesRead < length)
{
bytesRead += await stream.ReadAsync(buffer, bytesRead, buffer.Length - bytesRead);
if (bytesRead == 0)
{
throw new System.IO.IOException();
}
}
return buffer;
}
private byte[] ExecuteMessage(byte[] binaryData)
{
// Do something with message
return answer
}
public async void SendMessage(byte[] message)
{
byte[] buffer = new byte[message.Length + 4];
byte[] length = IntToFourBytes(message.Length);
length.CopyTo(buffer, 0);
message.CopyTo(buffer, 4);
await stream.WriteAsync(buffer, 0, buffer.Length);
}
private int FourBytesToInt(byte[] array)
{
int res = 0;
res += array[0] * 256 * 256 * 256;
res += array[1] * 256 * 256;
res += array[2] * 256;
res += array[3];
return res;
}
private byte[] IntToFourBytes(int intValue)
{
byte[] array = new byte[4];
array[0] = (byte)(intValue >> 24);
array[1] = (byte)(intValue >> 16);
array[2] = (byte)(intValue >> 8);
array[3] = (byte)intValue;
return array;
}
}
Radši založ nové vlákno s konkrétní otázkou. Problém s využitím CPU se zdá být vyřešený.
Mimochodem to while (true) v constructoru ti dává smysl? Takto nikdy nedojde k vytvoření...
Ty mas novou profilovku? Ty jo, sluší.
Díky skoro takhle vypadám i real life.
Jen míň barevnej, že? A to nejsem rasista
Ja jsem takhle bledej. Hlavne ma jiny vlasy.
Zobrazeno 28 zpráv z 28.