Silverlight: Stack Overflow async-IO:lla

in

Yllätyin hieman, kun koodailin erästä Silverlight 3 -projektia, joka käyttää asynkronisia lukuoperaatioita HTTP-yhteyksissä. Luettuaan melko suuren määrän dataa verkosta ohjelma sai yhtäkkiä Stack Overflow -virheilmoituksen, vaikkei sille ollut mitään järjellistä selitystä.

Pienellä googlailulla kävi ilmi, että .NETin asynkroniset operaatiot eivät olekaan aina asynkronisia. Joskus Stream.BeginRead() suorittaa lukuoperaation välittömästi synkronisesti, jolloin callback-metodia kutsutaan jo ennen kuin BeginRead() palautuu.

.NETin "luonnollinen" tapa suorittaa asynkronisia operaatioita on tällainen:

Stream stream;
AsyncCallback callback = new AsyncCallback(ReadComplete);

void StartReading()
{
  stream.BeginRead(callback, null);
}

void ReadComplete(IAsyncResult result)
{
  int nBytes = stream.EndRead(result);
  stream.BeginRead(callback, null);
}

Hetken pohtimalla koodista huomaa, että jos BeginRead() kutsuu aina ReadComplete():a ennen palautumistaan, niin tämä johtaa jatkuvaan rekursioon ja ajan myötä aiheuttaa stack overflown.

Oikea tapa on tarkastaa IAsyncResult.CompletedSynchronously -muuttujasta, oliko operaatio todellisuudessa synkroninen. Tällöin voidaan välttää rekursio esimerkiksi näin:

Stream stream;
AsyncCallback callback = new AsyncCallback(ReadComplete);

void StartReading()
{
  IAsyncResult result = stream.BeginRead(callback, null);
  if (result.CompletedSynchronously) ReadCompleteAsync(result);
}

void ReadComplete(IAsyncResult result)
{
  if (!result.CompletedSynchronously) ReadCompleteAsync(result);
}

void ReadCompleteAsync(IAsyncResult result)
{
  while (true) {
    int nBytes = stream.EndRead(result);
    result = stream.BeginRead(callback, null);
    if (!result.CompletedSynchronously) return;
  }
}

Ylläolevassa esimerkissä synkroniset operaatiot ajetaan peräkkäin yhden while (true) -luupin sisällä, joten rekursio vältetään.

PS. Tämä koodi on vain esimerkki, sitä ei ole testattu.

10 Comments
billigflieger 10.5.2009 11:13:04

Thanks. Nice article

Tommi F. 11.5.2009 10:59:52

Kiitokset tästä, vaikka ongelma ei olekaan vielä kävellyt itsellä vastaan (vaikka tulee asynkronisia http-kutsuja koodailtua C#:lla eri .NET-ympäristöihin tuon tuosta), niin pistetään ehdottomasti muistiin, jotta jatkossa muistan tehdä kutsut tähän tyyliin. Toki esimerkkikoodin ensin läpi ruotien ja testaten.

Anonymous 11.5.2009 16:33:39

En ole enää pariin vuoteen koodaillut mutta muistaakseni .NETin asynkroonisissa toiminnoissa on paljon puutteita. Jollei sitten tuonaikaisia puutteita ole korjattu.

Perusperiaate minun teollisuussoftissa oli se, että pitää voida määrittää tietty aikaraja mihinkä mennessä asynkroonisen toiminnan on ryhdyttävä toimimaan. Eli jos serverin pää ei vastaa sanotaan vaikkapa 5 sekunnissa, niin koko operaatio on pystyttävä perumaan ja myös niin, että muisti vapautuu - no ainakin sitten muistin tulee vapautua, kun gc päättää ryhtyä putsaamaan muistia.

Mutta näin vaan oli, että jos asynkrooninen operaatio ei tuottanut tulosta, niin toimintoon osallistuneet objektit jäivät jököttömään ikuisiksi ajoiksi heappiin.

Kennun esimerkki ei kelpaisi teollisuuden softaksi, sillä teollisuuden softissa pitää aina varmistaa, että operaatiot käynnistyvät, kuinka etenevät ja että varmasti loppuvat. Lisäksi maksimiviiveet mukaan jokaisen valvonnan vaiheeseen.

kennu 11.5.2009 17:47:50

Anonymous: Yhdyn näkemykseesi. Timeoutit ovat ehkä vähän eri asia kuin asynkroninen I/O, mutta ne pitää toki käsitellä, jos sovellus sitä vaatii.

Silverlightissä on lisäksi sellainenkin ongelma, että WebClientillä tehdyt haut lataavat oletuksena koko tiedoston/streamin ensin muistiin, ja palauttavat vasta sitten siihen osoittavan streamin. Tämän voi välttää asettamalla WebClient.AllowReadStreamBuffering = false, jolloin hakua ei puskuroida, vaan streami tulee reaaliajassa.

Anonymous 11.5.2009 20:05:40

Kennu sanoi : " Yhdyn näkemykseesi. Timeoutit ovat ehkä vähän eri asia kuin asynkroninen I/O, mutta ne pitää toki käsitellä, jos sovellus sitä vaatii. "

Niin, minä vaan aikoinani koodarina ihmettelin (tuloksettomasti) miksi tällaisiin työkaluihin kuten .NET, ei ole liitetty mahdollisuutta valvoa asynkronisten operaatioiden etenemisen mahdollisuutta esim. tiimeouteilla.

Tiedä sitten, kuinka asiat nyt edistyvät .NET-puolella kun moniydinprosessorit alkavat olla arkipäivää ja kenties kääntäjät + kirjastot alkavat tukemaan vaativiakin vaatimuksia - mita timeout-käsitteeseen tulee.

kennu 12.5.2009 00:14:50

@Anonymous: Jos kyseessä on oikeasti asynkroninen I/O, niin lienee aika luonnollista hoitaa timeoutit käyttöliittymän UI-threadissa timer-objekteilla. Siis ajastus hälyttämään 10 sekunnin päästä, asynkroninen luku käyntiin, ja timeristä CancelAsync() jos asioita ei tapahtunut ajoissa.

Synkronisessa I/O:ssahan tuo tuppaa olemaan hieman hankalampaa, kun pitää käyttää jotain select()-tyyppistä timeouttaavaa pollailua tai threadeja.

Anonymous 12.5.2009 01:08:55

Kennu sanoi:

" Anonymous: Jos kyseessä on oikeasti asynkroninen I/O, niin lienee aika luonnollista hoitaa timeoutit käyttöliittymän UI-threadissa timer-objekteilla. "

No just noin, mutta minulta vaan unohtui mainita, että tietenkin client-päästä on kysymys, kun asynkronisen operaation valvontaa valvotaan minkä client herätti serverin päähän. Mutta kun tuppaa myös olemaan niin, että kun asynkrooninen operaatio on herätety serverin päähän clientin toimesta, ja kun jostain syystä clientin herättämä async-operaatio serverin päässä keskeytyy, niin saattaa serverinkin päässä homma jäämään kesken, ja kasvattamaan muistin määrän käyttöä serverin päässä, kun client-pää ei pysty lähettämään serverille serverin muistia vapauttavaa "käskyä".

Mutta, mutta. Näetkö asynkronisen toiminnan ennemmenkin client- kuin serverin pään tekniikaksi. Että tavallaan client-päästä voisi ohjata serveriä niin halutessaan tekemään asynkroonisia toiminteita riippumatta siitä, miten serverin pää clientin toiminteen toteuttaa.

Minun mielestä client-päässä ei paljoakaan asynkroonisia toiminteita tarvita verrattuna server-päähän. Toki voi olla toisinkin, että molemmissa päissä tulee asynkroonisten toiminpiteiden toimia kuten ollaan synkroonisessakin maailmassa totuttu.

kennu 12.5.2009 01:40:47

@Anonymous: yleensä työskentelen serveripuolella jonkin alustan päällä, joka huolehtii matalan tason I/O:sta (Apache/PHP, streaming-palvelimet, SIP/XMPP/IRC jne palvelimet). En ole siis törmännyt tilanteeseen, että pitäisi itse murehtia asynkronisesta I/O:sta muuta kuin clienttipäässä.

Muutaman kerran tosin olen joitakin socket-palvelimia rakentanut asynkronisella I/O:lla, ja silloinkaan en ole kyllä välittänyt timeouteista, kun ei ole ollut tuotannosta kyse. Asiakas-socket on vain jäänyt "roikkumaan". Mutta kaipa siihen voisi soveltaa samaa ideaa, että käytetään pääthreadissa jotain ajastusmekanismia, joka keskeyttelee liian pitkään kestäneitä operaatioita.

Anonymous 12.5.2009 22:57:33

Kennu sanoi: " En ole siis törmännyt tilanteeseen, että pitäisi itse murehtia asynkronisesta I/O:sta muuta kuin clienttipäässä. "

Joo. Se kun on useimmissa client-server-ympäristöissä (ehkä vanha käsite) niin, että serverin päätä hoitavat adminit ja server-koneet ovat ainakin etäyhteyden päässä niin, että niitä voidaan "huolehtia".

Mutta on todella pirullinen tapaus se, että server-päästä ei huolehdi yksikään ihminen eikä serverille sallita etähallintaa. Asiat mutkistuvat kun server-pään koneen tulee toimia aina ja iäti. Ainoastaan verkkoyhteyden katkeaminen, hardiksen rikkoutuminen taikka taivaanpuotamien serverin päälle ovat sallituja tapauksia siihen, miksi clientin päästä ei saada yhteyttä serveriin.

Ja varsinkin silloin em. yhtälö tulee hankalaksi, kun server/serverit on Etelä-Amerikassa ja clientit ovat Euroopassa ja servereitä käsittelemään vikatapauksissa ei pääse ilman lupaa edes ohjelmiston valmistaja omallakaan kustannuksellaan. Toki watvh-dogit toimivat ja boottavat serverit paikallisesti kun tarpeeksi hankala tilanne on saavutettu.

Niin. Miksi aloin Kennun palstalla ottamaan kantaa alkuperäiseen kirjoitukseen oli se, että olisi todella jo vihdoinkin aika raikasta, että suomalaisilla softa-sivustoilla ryhdyttäisiin vihdoinkin keskustelemaan "laadukkaasta" ohjelmistojen kehityksestä ja niistä ongelmista, mitä todellisissa ohjelmistojen kehityksien ympäristöissä koetaan.

MSDN ja Google kun ei paljoa tarjoa oikeiden ongelmien puimiseen.

Päätän osaltani keskustelun asynkronisista toiminteista tähän.

Geldanlagen vergleichen 25.2.2010 12:18:25

Thanks for the info, thought it was the theme!


You can use Markdown to format your comment:

  • > quoted text
  • *italic* text
  • **bold** text
  • `code block` (multi-line is ok, whitespace is preserved)
  • [link text](http://www.google.com "link title")

Separate paragraphs in your text with two newlines