为什么在.Net中使用异步编程模型不会导致StackOverflow异常?

为什么在.Net中使用异步编程模型不会导致StackOverflow异常?,.net,asynchronous,.net,Asynchronous,例如,我们调用BeginReceive,并让BeginReceive在完成后执行回调方法。在我看来,如果这个回调方法再次调用BeginReceive,它将非常类似于递归。如何确保这不会导致stackoverflow异常。MSDN中的示例代码: private static void Receive(Socket client) { try { // Create the state object. StateObject state = new State

例如,我们调用BeginReceive,并让BeginReceive在完成后执行回调方法。在我看来,如果这个回调方法再次调用BeginReceive,它将非常类似于递归。如何确保这不会导致stackoverflow异常。MSDN中的示例代码:

private static void Receive(Socket client) {
    try {
        // Create the state object.
        StateObject state = new StateObject();
        state.workSocket = client;

        // Begin receiving the data from the remote device.
        client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(ReceiveCallback), state);
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

private static void ReceiveCallback( IAsyncResult ar ) {
    try {
        // Retrieve the state object and the client socket 
        // from the asynchronous state object.
        StateObject state = (StateObject) ar.AsyncState;
        Socket client = state.workSocket;

        // Read data from the remote device.
        int bytesRead = client.EndReceive(ar);

        if (bytesRead > 0) {
            // There might be more data, so store the data received so far.
        state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));

            // Get the rest of the data.
            client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,
                new AsyncCallback(ReceiveCallback), state);
        } else {
            // All the data has arrived; put it in response.
            if (state.sb.Length > 1) {
                response = state.sb.ToString();
            }
            // Signal that all bytes have been received.
            receiveDone.Set();
        }
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
}

因为
BeginReceive
调用另一个任意线程-每个线程都包含自己的堆栈。即使相同的代码正在运行,堆栈在给定线程上的深度也永远不会导致异常。如果对另一个线程的调用是非阻塞的,堆栈将展开—它使调用继续正常进行。如果他们每个人都等待,但再也没有回来,你会遇到麻烦


在您的示例中,根据代码路径,它可能会永远运行。这个想法类似于co例程,其中方法不断地以粗糙的乒乓方式相互调用,但同样没有堆栈问题。

BeginReceive注册一个与重叠IO操作相关的回调函数。当数据可用时,操作系统将调用回调,但BeginReceive调用将立即返回,因此您对ReceiveCallback的调用也将完成。

将实际的IO想象为发生在一个不属于您的线程中,而是发生在操作系统中。您注册回调的行为只是说“发生事情时继续给我打电话”,但它不会被添加到堆栈中。这就是它被称为异步的原因。

这是一个有趣的问题,但在调用
BeginReceive
之后,函数继续执行,然后最终返回,因此那里没有真正的递归。

使用

client.BeginReceive(state.buffer,0,StateObject.BufferSize,0, 
    new AsyncCallback(ReceiveCallback), state);
自动调用ReceiveCallback方法,该方法在操作完成时调用

同时,调用
BeginReceive
的方法继续执行,做任何事情,并愉快地返回,从而将自己从堆栈中移除


使用递归时,每次调用都会添加一个堆栈帧(请参见注释),在被调用的方法返回之前,该堆栈不会弹出,因此,在递归完成之前,堆栈会一直增长。

递归不受方法结构的约束。Axum中有使用这种风格的递归示例。有趣的问题是,我也想知道这一点-但出于某种原因,我确信
async
方法是不同的。[学究]当使用递归时,编译器没有(或不能)应用尾部优化,每次调用都会添加堆栈帧。这种差异使得惯用函数代码可以避免堆栈溢出(.NET在某些情况下包括支持)。那么异步方法在堆栈方面是如何工作的呢?还有回调有多贵,它是如何工作的?它还需要复制所有的类变量吗?正如有人提到的,异步回调不能保证在同一个线程上执行,因此您可能有另一个堆栈。除此之外,他们就像其他任何方法一样。。。我还没有测试使用异步调用是否会对性能产生影响,但我的观点是,即使开销增加,异步方法也会使应用程序更具响应性。因此,如果方法调用没有被推到堆栈上,它是如何工作的?@uriDium在下一批数据可用时,它会被推到堆栈上。这就是为什么堆栈不会在每次调用时增长,并且没有溢出。@uriDium哪个方法?回拨还是开始接收?BeginReceive进入堆栈,注册回调,然后返回(并从堆栈中删除)。所以操作系统的线程堆栈是这样的:1)接收一些io 2)接收回调3)开始接收。就这样,再也没有比这更深的了。您是否熟悉JavaScript中的setTimeout()函数?