Ist es eine hybride Art von Dingen? (z. B. verwendet mein .NET-Programm einen Stapel, bis es einen asynchronen Aufruf ausführt, und wechselt dann bis zum Abschluss zu einer anderen Struktur. Zu diesem Zeitpunkt wird der Stapel wieder in einen Zustand abgewickelt, in dem er sicher sein kann, dass die nächsten Elemente usw. vorhanden sind. )
Grundsätzlich ja.
Angenommen, wir haben
async void MyButton_OnClick() { await Foo(); Bar(); }
async Task Foo() { await Task.Delay(123); Blah(); }
Hier ist eine extrem vereinfachte Erklärung, wie die Fortsetzungen verdichtet werden. Der eigentliche Code ist erheblich komplexer, aber dies vermittelt die Idee.
Sie klicken auf die Schaltfläche. Eine Nachricht wird in die Warteschlange gestellt. Die Nachrichtenschleife verarbeitet die Nachricht und ruft den Klick-Handler auf, wobei die Rücksprungadresse der Nachrichtenwarteschlange auf dem Stapel abgelegt wird. Das heißt, was passiert, nachdem der Handler fertig ist, ist, dass die Nachrichtenschleife weiterlaufen muss. Die Fortsetzung des Handlers ist also die Schleife.
Der Klick-Handler ruft Foo () auf und legt die Rücksprungadresse von sich selbst auf den Stapel. Das heißt, die Fortsetzung von Foo ist der Rest des Klick-Handlers.
Foo ruft Task.Delay auf und legt die Rücksprungadresse von sich auf den Stapel.
Task.Delay führt alle erforderlichen Aktionen aus, um eine Task sofort zurückzugeben. Der Stapel ist geknallt und wir sind wieder in Foo.
Foo überprüft die zurückgegebene Aufgabe, um festzustellen, ob sie abgeschlossen ist. Es ist nicht. Die Fortsetzung des Wartens besteht darin, Blah () aufzurufen. Foo erstellt also einen Delegaten, der Blah () aufruft, und signiert diesen Delegaten als Fortsetzung der Aufgabe. (Ich habe gerade eine leichte falsche Aussage gemacht. Hast du sie verstanden? Wenn nicht, werden wir sie gleich enthüllen.)
Foo erstellt dann ein eigenes Task-Objekt, markiert es als unvollständig und gibt es im Stapel an den Click-Handler zurück.
Der Klick-Handler untersucht die Aufgabe von Foo und stellt fest, dass sie unvollständig ist. Die Fortsetzung des Wartens im Handler besteht darin, Bar () aufzurufen. Der Click-Handler erstellt also einen Delegaten, der Bar () aufruft, und legt ihn als Fortsetzung der von Foo () zurückgegebenen Aufgabe fest. Anschließend wird der Stapel in die Nachrichtenschleife zurückgeführt.
Die Nachrichtenschleife verarbeitet weiterhin Nachrichten. Schließlich macht die von der Verzögerungsaufgabe erzeugte Timer-Magie ihre Sache und sendet eine Nachricht an die Warteschlange, die besagt, dass die Fortsetzung der Verzögerungsaufgabe jetzt ausgeführt werden kann. Die Nachrichtenschleife ruft also die Taskfortsetzung auf und legt sich wie gewohnt auf den Stapel. Dieser Delegierte ruft Blah () an. Blah () macht das, was es macht und gibt den Stapel zurück.
Was passiert nun? Hier ist das Knifflige. Die Fortsetzung der Verzögerungsaufgabe ruft nicht nur Blah () auf. Es muss auch einen Aufruf von Bar () auslösen , aber diese Aufgabe weiß nichts über Bar!
Foo hat tatsächlich einen Delegaten erstellt, der (1) Blah () aufruft und (2) die Fortsetzung der Aufgabe aufruft, die Foo erstellt und an den Ereignishandler zurückgegeben hat. So rufen wir einen Delegaten auf, der Bar () aufruft.
Und jetzt haben wir alles getan, was wir tun mussten, in der richtigen Reihenfolge. Wir haben jedoch nie lange aufgehört, Nachrichten in der Nachrichtenschleife zu verarbeiten, sodass die Anwendung weiterhin reagierte.
Dass diese Szenarien für einen Stapel zu weit fortgeschritten sind, ist durchaus sinnvoll, aber was ersetzt den Stapel?
Ein Diagramm von Aufgabenobjekten, die über die Abschlussklassen von Delegaten Verweise aufeinander enthalten. Diese Schließungsklassen sind Zustandsautomaten, die die Position des zuletzt ausgeführten Wartens und die Werte der Einheimischen verfolgen. In dem angegebenen Beispiel außerdem eine Warteschlange mit globalen Status von Aktionen, die vom Betriebssystem implementiert wurden, und die Nachrichtenschleife, die diese Aktionen ausführt.
Übung: Wie funktioniert das alles in einer Welt ohne Nachrichtenschleifen? Zum Beispiel Konsolenanwendungen. Warten in einer Konsolen-App ist ganz anders; Können Sie aus dem, was Sie bisher wissen, ableiten, wie es funktioniert?
Als ich vor Jahren davon erfahren hatte, war der Stack da, weil er blitzschnell und leicht war, ein Stück Speicher, der bei der Anwendung außerhalb des Heaps zugewiesen wurde, weil er eine hocheffiziente Verwaltung für die jeweilige Aufgabe unterstützte (Wortspiel beabsichtigt?). Was hat sich geändert?
Stapel sind eine nützliche Datenstruktur, wenn die Lebensdauer von Methodenaktivierungen einen Stapel bildet, aber in meinem Beispiel bilden die Aktivierungen des Klick-Handlers Foo, Bar und Blah keinen Stapel. Daher kann die Datenstruktur, die diesen Workflow darstellt, kein Stapel sein. Vielmehr handelt es sich um ein Diagramm mit Heap-zugewiesenen Aufgaben und Delegaten, das einen Workflow darstellt. Die Wartezeiten sind die Punkte im Workflow, an denen im Workflow keine weiteren Fortschritte erzielt werden können, bis die zuvor begonnenen Arbeiten abgeschlossen sind. Während wir warten, können wir andere Arbeiten ausführen , die nicht von den abgeschlossenen Aufgaben abhängen.
Der Stapel ist nur ein Array von Frames, wobei Frames (1) Zeiger auf die Mitte von Funktionen (wo der Aufruf stattgefunden hat) und (2) Werte lokaler Variablen und Temps enthalten. Die Fortsetzung von Aufgaben ist dasselbe: Der Delegat ist ein Zeiger auf die Funktion und hat einen Status, der auf einen bestimmten Punkt in der Mitte der Funktion verweist (wo das Warten stattgefunden hat), und der Abschluss enthält Felder für jede lokale Variable oder temporäre Variable . Die Frames bilden einfach kein schönes, ordentliches Array mehr, aber alle Informationen sind gleich.