Ich habe ein zerbrechliches Verständnis davon, wie das await
Schlüsselwort funktioniert, und ich möchte mein Verständnis davon ein wenig erweitern.Rekursion und die Erwartung/async Keywords
Das Problem, das meinen Kopf noch dreht, ist die Verwendung von Rekursion. Hier ein Beispiel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestingAwaitOverflow
{
class Program
{
static void Main(string[] args)
{
var task = TestAsync(0);
System.Threading.Thread.Sleep(100000);
}
static async Task TestAsync(int count)
{
Console.WriteLine(count);
await TestAsync(count + 1);
}
}
}
Dieses offensichtlich StackOverflowException
wirft.
Mein Verständnis ist, weil der Code tatsächlich synchron läuft bis die erste asynchrone Aktion, nach der es ein Task
Objekt zurückgibt, das Informationen über den asynchronen Vorgang enthält. In diesem Fall gibt es keine asynchrone Operation, so dass es nur unter dem falschen Versprechen weiterläuft, dass es schließlich eine Task
zurückgegeben bekommt.
Jetzt ist es nur ein kleines bisschen zu ändern:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestingAwaitOverflow
{
class Program
{
static void Main(string[] args)
{
var task = TestAsync(0);
System.Threading.Thread.Sleep(100000);
}
static async Task TestAsync(int count)
{
await Task.Run(() => Console.WriteLine(count));
await TestAsync(count + 1);
}
}
}
Dieses kein StackOverflowException
wirft. Ich kann sortof sehen, warum es funktioniert, aber ich würde es eher ein Bauchgefühl nennen (es geht wahrscheinlich damit um, wie der Code Callbacks verwenden soll, um den Stack zu vermeiden, aber ich kann dieses Bauchgefühl nicht in einen übersetzen Erklärung)
So habe ich zwei Fragen:
- Wie funktioniert die zweite Charge von Code ein
StackOverflowException
vermeiden? - Vergeudet der zweite Stapel von Code andere Ressourcen? (Zum Beispiel weist es eine absurd große Anzahl von Aufgabenobjekten auf dem Haufen zu?)
Danke!
Also, wenn ich die 'Task.Run()' mit einer Funktion ersetzt, die sofort ein abgeschlossenes 'Task'-Objekt zurückgab, würde es die Stapelüberlauf-Ausnahme wieder einführen? (Oder habe ich gerade etwas vorgeschlagen, was nicht möglich ist?) – riwalk
@ Stargazer712 Nicht nur ist es möglich, es würde tatsächlich tun, was du beschreibst. Probieren Sie 'aware Task.FromResult
@ Stargazer712 ja! Und wenn Sie es mit 'Task.Yield()' ersetzen (was garantiert, dass Sie eine Fortsetzung buchen müssen), würden Sie garantiert frei von Stack-Überläufen sein (bei einer Performance-Kosten). Beachten Sie, dass "Yield" eine erwartete andere als "Task" zurückgibt. Es ist viel schwieriger, diese Garantie von einer Aufgabe zu erhalten, da Sie sicherstellen müssen, dass sie nicht abgeschlossen wird, wenn Sie von der Funktion "Warten" abgefragt werden. – usr