Was ist die richtige Art zu verwenden async/await in eine rekursive Methode?
Was ist die richtige Art zu verwenden async/await in eine rekursive Methode? Hier ist meine Methode:
public string ProcessStream(string streamPosition)
{
var stream = GetStream(streamPosition);
if (stream.Items.count == 0)
return stream.NextPosition;
foreach(var item in stream.Items) {
ProcessItem(item);
}
return ProcessStream(stream.NextPosition)
}
Und hier ist die Methode mit dem async/await:
public async Task<string> ProcessStream(stringstreamPosition)
{
var stream = GetStream(streamPosition);
if (stream.Items.count == 0)
return stream.NextPosition;
foreach(var item in stream.Items) {
await ProcessItem(item); //ProcessItem() is now an async method
}
return await ProcessStream(stream.NextPosition);
}
- Sehen Sie eventuelle Fehler mit, was du versucht hast?
- Nope, scheint einwandfrei zu funktionieren. Ich wurde gerade gefragt, ob es irgendeine mögliche Gefahr.
- Wie ist mein code synchron? Mit dem await-Schlüsselwort enthält, wird die Methode zurückgeben soll, sofort, um die Anrufer richtig? Ich habe nur angefangen zu lernen, async/await heute, so gibt es wahrscheinlich eine Menge Lücken in meinem Verständnis.
- ja, es ist mein Fehler
- Sieht ziemlich korrekt zu mir. Nicht sicher, was Sie erwarten sind Sie hier.
- Du bist fehlen einige Zeilenende-Zeichen (
;
), und mit Ihrstream.NextPosition
als ob es war einstring
wenn Sie zurück sind, aberint
wenn Sie es weitergeben zuProcessStream
. Jetzt bekamen wir, dass aus der Art und Weise, diese Methode ist trivial zu implementieren, wie eine einfache Schleife, die Vermeidung von Rekursion zusammen - und das ist eine gute Idee, weil die in synchronen Szenarien (einschließlich der Fälle, in denen rekursiveTask
s synchron abgeschlossen) Rekursion kann dazu führenStackOverflowException
s. Wahrscheinlich wird nicht passieren, in Ihrem Fall, aber warum würden Sie sogar das Risiko, wenn Sie können, verwenden Sie eine einfache Schleife, ist mir schleierhaft. - vielen Dank für die Hinweise. Ich habe korrigiert den code. Würden Sie bitte mir ein code-snippet für das nicht-rekursive Weise?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Während ich zu Anfang sagen, dass die Absicht, die Methode ist mir nicht ganz klar, reimplementing es mit einer einfachen Schleife ist ziemlich trivial:
Rekursion ist nicht stapelbar,-freundlich-und wenn Sie die Möglichkeit haben, mit Hilfe einer Schleife, es ist etwas, auf jeden Fall lohnt ein Blick in die in einfachen synchronen Szenarien, in denen schlecht kontrollierte Rekursion führt schließlich zu
StackOverflowException
s) als auch asynchrone Szenarien, wo, ich werde ehrlich zu sein, ich weiß gar nicht, was passieren würde, wenn Sie schieben die Dinge zu weit (meine VS Test-Explorer stürzt ab immer wenn ich versuche zu reproduzieren bekannt stack-overflow-Szenarien mitasync
Methoden).Antworten wie Rekursion und der await /async-Keywords vorschlagen, dass
StackOverflowException
ist weniger ein problem mitasync
aufgrund der Art und Weise derasync/await
Zustand, Maschine funktioniert, aber dies ist nicht etwas, was ich untersucht haben, viel, weil ich meistens bei der Rekursion zu vermeiden, Wann immer möglich.stream.Items
enthalten. Thread-Sicherheit ist von größter Bedeutung, und Sie müssen, um sicherzustellen, dass mehrereProcessItems
parallel ausgeführt werden können. Darüber hinaus, wenn die Anzahl der Elemente ist nicht von Bedeutung, aber jeder braucht lange Zeit, um zu verarbeiten, würde ich anstelle derforeach
mitawait Task.WhenAll(stream.Items.Select(ProcessItem));
. Es die Anzahl der Elemente groß ist und die Verarbeitung schnell, ich würde zurückkehren, um die synchrone Verarbeitung und die VerwendungParallel.ForEach
statt.Task
s sind Recht "schwer" und Sie wollen wirklich nicht einen pro Element beim Umgang mit große Sammlungen.Parallel.ForEach
nutzt die smart-Sammlung, die Partitionierung zu erreichen Parallelisierung ohne viel Aufwand (nicht mitgerechnet die Fälle, wo Sie es zu tun haben mit wirklich kleine delegieren Körper), und bietet eine Reihe von optionalen tweaks und Optimierungen (d.h. lokaler Zustand), die Sie in Aussehen sollte, um zu quetschen das Letzte bisschen Leistung aus ihm heraus.GetStream
eine Weile dauern kann und Sie letztendlich wollen die Hersteller (GetStream
) und Verbraucher (foreach -> ProcessItem
) parallel laufen?Wenn ich code hinzufügen, um Ihr Beispiel zu konkretisieren, finde ich zwei mögliche Wege für die Rekursion zu schlecht ausgehen. Beide gehen davon aus, dass Ihre Daten ist ziemlich groß und benötigen bestimmte Bedingungen zum auslösen.
ProcessItem(string)
gibt eineTask
abgeschlossen werden, bevor esawait
ed auf (oder ich nehme an, es abgeschlossen, bevor dieawait
beendet Spinnen), die Fortsetzung wird synchron ausgeführt. In meinem code unten, habe ich simuliert dies, indem erProcessItem(string)
zurückTask.CompletedTask
. Wenn ich das mache, das Programm sehr schnell beendet mit einemStackOverflowException
. Dies ist weil .net TPL "Veröffentlichungen Zalgo" von opportunistisch Ausführung Fortsetzungen synchron ohne Rücksicht auf wie viel Platz steht in der aktuellen Stapel. Das bedeutet, dass es verschärfen das Potenzial Stapelspeicher Problem, dass Sie bereits durch die Verwendung eines rekursiven Algorithmus. Um dies zu sehen, kommentieren Sieawait Task.Yield();
in meinem code-Beispiel unten.Task.Yield()
), schließlich läuft das Programm aus der Erinnerung und sterben mit einemOutOfMemoryException
. Wenn ich das richtig verstehe, würde das nicht passieren, wennreturn await
waren in der Lage zu emulieren, die tail-call-Optimierung. Ich kann mir vorstellen, dass das, was hier passiert, ist jeder Aufruf erzeugt so etwas wie eine BuchführungTask<string>
und hält Generierung von Ihnen, obwohl Sie konnte sein verschmolz. Diesen Fehler zu reproduzieren mit dem Beispiel unten, sicherzustellen, dass Sie das Programm läuft als 32-bit, deaktivieren Sie dieConsole.WriteLine()
nennen (weil Konsolen sind wirklich sehr langsam), und sorgen für dieawait Task.Yield()
ist aufgehoben.So, ich denke, meine zwei Empfehlungen sind:
Task.Yield()
wenn Sie nicht sicher, dass der Stapel Wachstum der Rekursion unterbrochen wird durch etwas anderes.