2011-01-14 2 views
5

Por ejemplo, llamamos a BeginReceive y tenemos el método de devolución de llamada que BeginReceive ejecuta cuando se ha completado. Si ese método de devolución de llamada una vez más llama a BeginReceive en mi mente, sería muy similar a la recursión. ¿Cómo es que esto no causa una excepción de stackoverflow? Ejemplo de código de MSDN:¿Por qué el uso del Modelo de programación asíncrono en .Net no da lugar a excepciones de StackOverflow?

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()); 
    } 
} 
+0

Pregunta interesante, me pregunté sobre eso también, pero por alguna razón me convencí de que los métodos 'async' son diferentes. – VoodooChild

Respuesta

3

BeginReceive registra una función de devolución de llamada asociada con una operación IO solapada. El sistema operativo llamará a la devolución de llamada cuando los datos estén disponibles, pero la llamada BeginReceive regresa inmediatamente y, por lo tanto, su invocación de ReceiveCallback también finaliza.
Piense en el IO real como si sucediera en un hilo que no le pertenece, sino en el sistema operativo. Su acto de registrar la devolución de llamada solo dice "sigue llamándome cuando pase algo", pero no se agrega a la pila. Es por eso que se llama asincrónico.

+0

Entonces, si la llamada al método no se inserta en la pila, ¿cómo funciona? – uriDium

+0

@uriDium se empuja a la pila cuando el siguiente lote de datos está disponible. es por eso que la pila no crece en cada llamada y no hay desbordamiento. – TheVillageIdiot

+0

@uriDium ¿qué método? ¿La devolución de llamada o el BeginReceive? BeginReceive va en la pila, registra la devolución de llamada, luego regresa (y se elimina de la pila). Así que la pila de subprocesos del SO es así: 1) __receive_some_io 2) ReceiveCallback 3) BeginReceive. Eso es todo, nunca es más profundo que eso. ¿Está familiarizado con la función setTimeout() en JavaScript por casualidad? –

2

Debido a que los BeginReceive llamadas sobre otra hilo arbitrario - cada hilo contiene su propia pila. Aunque se está ejecutando el mismo código, la pila nunca se profundiza lo suficiente en un subproceso dado como para causar una excepción. La pila se desenrolla si la llamada al otro hilo no es bloqueante: hace que la llamada continúe normalmente. Tendría problemas si cada uno esperaba, pero nunca regresó.

En su ejemplo, dependiendo de la ruta del código, podría decirse que se ejecutará para siempre. La idea es similar a las rutinas conjuntas, donde los métodos se llaman constantemente entre sí de una forma cruda de ping-pong, pero de nuevo no hay problemas con la pila.

3

Una pregunta interesante, pero después de llamar al BeginReceive, su función continúa ejecutándose y luego regresa, por lo que no hay recursión real allí.

+0

recursión no está dictada por la estructura del método. Hay muestras recursivas en Axum que usan este estilo. –

1

Usando

client.BeginReceive(state.buffer,0,StateObject.BufferSize,0, 
    new AsyncCallback(ReceiveCallback), state); 

hace no automáticamente llame al método ReceiveCallback, ese método se llama cuando se termina la operación.

Mientras tanto, el método que llamó al BeginReceive continúa ejecutándose, haciendo lo que haga, y regresando felizmente, eliminándose de la pila.

Cuando utiliza la recursividad, cada llamada agrega un marco de la pila (vea el comentario), que no aparece hasta que devuelve el método llamado, por lo que la pila crece hasta que finaliza la recursión.

+0

[Pedante] Cuando utiliza recursividad, * y el compilador no aplica (o no puede) la optimización de cola *, cada llamada agrega un marco de pila. La diferencia permite que el código funcional idiomático evite los desbordamientos de pila (.NET incluye soporte en algunos casos). – Richard

+0

No vayamos allí :) – SWeko

+0

Entonces, ¿cómo funcionan los métodos asíncronos en términos de la pila? Además, ¿qué tan caro es la devolución de llamada y cómo funciona eso? ¿Necesita copiar todas las variables de clase también? – uriDium

Cuestiones relacionadas