2011-01-14 2 views
5

Zum Beispiel rufen wir BeginReceive auf und haben die Callback-Methode, die BeginReceive ausführt, wenn sie abgeschlossen ist. Wenn diese Callback-Methode wieder BeginReceive aufruft, wäre sie der Rekursion sehr ähnlich. Wie kommt es, dass dies nicht zu einer stackoverflow-Ausnahme führt? Beispielcode von MSDN:Warum führt die Verwendung des asynchronen Programmiermodells in .NET nicht zu StackOverflow-Ausnahmen?

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

Interessante Frage, ich habe mich auch gefragt - aber aus irgendeinem Grund habe ich mich davon überzeugt, dass Async-Methoden anders sind. – VoodooChild

Antwort

3

BeginReceive registriert eine Callback-Funktion, die einer überlappenden IO-Operation zugeordnet ist. Der Rückruf wird vom Betriebssystem aufgerufen, wenn die Daten verfügbar sind, aber der BeginReceive-Aufruf wird sofort zurückgegeben, und daher wird auch der Aufruf von ReceiveCallback beendet.
Denken Sie an die tatsächliche E/A in einem Thread, der nicht zu Ihnen gehört, sondern eher zum Betriebssystem. Ihr Vorgang der Registrierung des Rückrufs sagt nur: "Ruf mich immer an, wenn etwas passiert", aber es wird nicht zum Stapel hinzugefügt. Deshalb heißt es asynchron.

+0

Also, wenn der Methodenaufruf nicht auf den Stapel geschoben wird, wie funktioniert es? – uriDium

+0

@uriDium wird auf den Stapel geschoben, wenn die nächste Datenmenge verfügbar ist. Deshalb wächst der Stack nicht bei jedem Aufruf und es gibt keinen Überlauf. – TheVillageIdiot

+0

@uriDium welche Methode? Der Rückruf oder die BeginReceive? BeginReceive geht auf Stapel, registriert den Rückruf, kehrt dann zurück (und wird vom Stapel entfernt). Der Stack des Betriebssystem-Threads ist also folgendermaßen aufgebaut: 1) __receive_some_io 2) ReceiveCallback 3) BeginReceive. Das ist es, wird nie tiefer als das. Kennen Sie zufällig die Funktion setTimeout() in JavaScript? –

2

Da die BeginReceive Anrufe auf eine andere willkürliche Gewinde - jeder Thread seinen eigenen Stapel enthält. Obwohl derselbe Code ausgeführt wird, wird der Stapel nie tief genug für einen bestimmten Thread, um eine Ausnahme zu verursachen. Der Stapel wird abgewickelt, wenn der Anruf an den anderen Thread nicht blockiert ist - der Anruf wird dann normal fortgesetzt. Sie würden Probleme bekommen, wenn sie jeweils warten, aber nie zurückkommen.

In Ihrem Beispiel, je nach Code-Pfad - könnte es wohl für immer laufen. Die Idee ist ähnlich wie bei Co-Routinen, bei denen sich Methoden ständig in einer primitiven Ping-Pong-Manier aufrufen, aber wiederum keine Stapelprobleme.

3

Eine interessante Frage, aber nachdem Sie BeginReceive aufrufen, wird Ihre Funktion weiterhin ausgeführt und schließlich zurückgegeben, so dass dort keine echte Rekursion gibt.

+0

Rekursion wird nicht durch die Struktur der Methode diktiert. In Axum gibt es rekursive Samples, die diesen Stil verwenden. –

1

Mit

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

tut nicht automatisch die ReceiveCallback Methode aufrufen, diese Methode aufgerufen wird, wenn der Vorgang beendet ist.

In der Zwischenzeit wird die Methode, die die BeginReceive aufgerufen hat, weiterhin ausgeführt, was auch immer sie tut, und glücklich zurückkehren, wodurch sie sich vom Stapel entfernt.

Wenn Sie Rekursion verwenden, fügt jeder Aufruf einen Stack-Frame hinzu (siehe Kommentar), der nicht abgerufen wird, bis die aufgerufene Methode zurückkehrt, so dass der Stapel wächst, bis die Rekursion abgeschlossen ist.

+0

[Pedantisch] Wenn Sie Rekursion * verwenden und der Compiler die Tail-Optimierung * nicht anwendet (oder nicht kann), fügt jeder Aufruf einen Stack-Frame hinzu. Der Unterschied erlaubt idiomatischen Funktionscode, um Stack-Überläufe zu vermeiden (.NET enthält in einigen Fällen Unterstützung). – Richard

+0

Lass uns nicht dorthin gehen :) – SWeko

+0

Wie also funktionieren asynchrone Methoden in Bezug auf den Stack? Wie teuer ist der Rückruf und wie funktioniert das? Muss es auch alle Klassenvariablen kopieren? – uriDium

Verwandte Themen