Mam słabe pojęcie o tym, jak działa słowo kluczowe await
i chcę nieco rozszerzyć moje zrozumienie.Rekurencja i oczekiwanie/asynchronizacja Słowa kluczowe
Kwestią, która wciąż sprawia, że moje obroty głową są użycie rekursji. Oto przykład:
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);
}
}
}
Ten oczywiście rzuca StackOverflowException
.
Moje zrozumienie jest takie, że kod faktycznie działa synchronicznie do pierwszego działania asynchronicznego, po czym zwraca obiekt Task
, który zawiera informacje o operacji asynchronicznej. W tym przypadku nie ma operacji asynchronicznej, a więc po prostu powtarza się pod fałszywą obietnicą, że w końcu otrzyma zwrócony kod w postaci Task
.
Teraz zmieniając go tylko odrobinę:
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);
}
}
}
Ten jeden nie rzucać StackOverflowException
. Mogę sortof zobaczyć, dlaczego to działa, ale nazwałbym to bardziej uczuciem jelitowym (prawdopodobnie zajmuje się tym, jak kod jest zorganizowany do korzystania z wywołań zwrotnych, aby uniknąć budowania stosu, ale nie mogę przetłumaczyć tego przeczucia na wyjaśnienie)
mam więc dwa pytania:
- jaki sposób druga partia kodu uniknąć się
StackOverflowException
? - Czy druga partia kodu marnuje inne zasoby? (na przykład czy przydziela się absurdalnie dużą liczbę obiektów zadań na stercie?)
Dzięki!
Więc jeśli zastąpiłem 'Task.Run()' funkcją, która natychmiastowo zwróciła ukończony obiekt 'Task', to ponownie wprowadził wyjątek przepełnienia stosu? (A może po prostu zaproponowałem coś, co nie jest możliwe?) – riwalk
@ Stargazer712 Nie tylko jest to możliwe, ale rzeczywiście zrobiłoby to, co opisujesz. Po prostu spróbuj 'poczekaj na Task.FromResult
@ Stargazer712 yes! A jeśli zastąpisz go 'Task.Yield()' (co gwarantuje, że będziesz musiał opublikować kontynuację), uzyskasz gwarancję, że nie będziesz mógł przepełnić stosu (kosztem wydajności). Zauważ, że 'Wydajność' zwraca wartość inną niż" Zadanie ". O wiele trudniej jest uzyskać tę gwarancję z zadania, ponieważ musiałbyś dopilnować, aby nie została ona ukończona po zapytaniu o funkcję oczekującą. – usr