I was reading Stephen Toub’s blog post on How Async/Await Really Works in C#. In the article he describes how this code (posted below) would cause a stack overflow to occur because the BeginRead
method would complete synchronously and invoke the AsyncCallback
lambda, which would invoke BeginRead
again etc…
He then provides possible solutions to this problem, one of which is:
Don’t allow the AsyncCallback to be invoked synchronously. If it’s always invoked asynchronously, even if the operation completes synchronously, then the risk of stack dives goes away. But so too does performance, because operations that complete synchronously (or so quickly that they’re observably indistinguishable) are very common, and forcing each of those to queue its callback adds measurable overhead.
This isn’t obvious to me. So my question is:
Why would asynchronous invocation of the callback prevent stack dives?
using System.Net;
using System.Net.Sockets;
using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
listener.Listen();
using Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.Connect(listener.LocalEndPoint!);
using Socket server = listener.Accept();
_ = server.SendAsync(new byte[100_000]);
var mres = new ManualResetEventSlim();
byte[] buffer = new byte[1];
var stream = new NetworkStream(client);
void ReadAgain()
{
stream.BeginRead(buffer, 0, 1, iar =>
{
if (stream.EndRead(iar) != 0)
{
ReadAgain(); // uh oh!
}
else
{
mres.Set();
}
}, null);
};
ReadAgain();
mres.Wait();
Nathanial Boehlje is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.