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.