W jaki sposób Task <int> staje się int?

116

Mamy tę metodę:

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();

   Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

   // You can do work here that doesn't rely on the string from GetStringAsync.
   DoIndependentWork();

   string urlContents = await getStringTask;
   //The thing is that this returns an int to a method that has a return type of Task<int>
   return urlContents.Length;
}

Czy niejawna konwersja występuje między Task<int>a int? Jeśli nie, to co się dzieje? Jak to działa?

Obywatel
źródło
1
Czytaj dalej . Zakładam, że kompilator zajmie się tym na podstawie asyncsłowa kluczowego.
D Stanley
1
@Freeman, spójrz na to świetne wyjaśnienie: stackoverflow.com/a/4047607/280758
qehgt

Odpowiedzi:

171

Czy niejawna konwersja występuje między Task <> i int?

Nie. To tylko część tego, jak async/ awaitdziała.

Każda metoda zadeklarowana jako asyncmusi mieć zwracany typ:

  • void (unikaj, jeśli to możliwe)
  • Task (brak wyniku poza powiadomieniem o zakończeniu / niepowodzeniu)
  • Task<T>(dla logicznego wyniku typu Tw sposób asynchroniczny)

Kompilator wykonuje wszystkie odpowiednie opakowania. Chodzi o to, że jesteś asynchronicznie powrocie urlContents.Length- nie może sprawić, że metoda po prostu wrócić int, jak rzeczywista metoda zwróci gdy trafi pierwszy awaitwyraz, który nie został już ukończony. Zamiast tego zwraca wartość, Task<int>która zakończy się po zakończeniu samej metody asynchronicznej.

Zauważ, że awaitrobi to odwrotnie - rozpakowuje a Task<T>do Twartości, tak jak działa ta linia:

string urlContents = await getStringTask;

... ale oczywiście rozpakowuje je asynchronicznie, podczas gdy samo użycie Resultspowoduje zablokowanie do zakończenia zadania. ( awaitmoże rozpakować inne typy, które implementują oczekiwany wzorzec, ale Task<T>jest to ten, którego prawdopodobnie będziesz używać najczęściej).

To podwójne zawijanie / rozpakowywanie umożliwia komponowanie asynchronii. Na przykład mógłbym napisać inną metodę asynchroniczną, która wywoła Twoją i podwaja wynik:

public async Task<int> AccessTheWebAndDoubleAsync()
{
    var task = AccessTheWebAsync();
    int result = await task;
    return result * 2;
}

(Lub po prostu return await AccessTheWebAsync() * 2;oczywiście.)

Jon Skeet
źródło
3
czy można podać jakieś szczegóły dotyczące tego, jak to działa pod maską, po prostu ciekawy.
Freeman
8
+1 Dobra odpowiedź, jak zawsze. A dlaczego tak szybko je piszesz ?!
Felix K.
9
+1: Właśnie zacząłem przeglądać async/ awaiti uważam, że jest to wyjątkowo nieintuicyjne. IMO, powinno być słowo kluczowe lub coś podobnego w returncelu wyjaśnienia, np. return async result;(W ten sam sposób, w jaki await result„rozpina się” Tz Tast<T>).
dav_i
2
@JonSkeet Ale to nie ma sensu bez await- gdy T foo = someTaskT;dostaniesz "Nie można niejawnie przekonwertować typu Task<T>na T" - w ten sam sposób, w jaki twierdzę, że bardziej sensowne byłoby posiadanie słowa kluczowego dla odwrotności (zawijanie Task<T>). Jestem za usuwaniem puchu, ale w tym przypadku wydaje mi się, że zapewnia to niepotrzebne zaciemnianie asyncmetod. (Oczywiście kwestia jest dyskusyjna, ponieważ uprawnienia, które już zostały wypowiedziane / zakodowane!)
dav_i
2
@dav_i: To zadanie nie ma sensu, ale reszta ma. Są też przypadki, w których całe stwierdzenie miałoby sens - chociaż może nie być przydatne. Biorąc pod uwagę, że metoda jest już zadeklarowana async, myślę, że to wystarczy.
Jon Skeet
18

Nie wymaga konwersji zadania na int. Po prostu użyj wyniku zadania.

int taskResult = AccessTheWebAndDouble().Result;

public async Task<int> AccessTheWebAndDouble()
{
    int task = AccessTheWeb();
    return task;
}

Zwróci wartość, jeśli jest dostępna, w przeciwnym razie zwróci 0.

Aniket Sharma
źródło
20
nie o to prosiłem.
Freeman,
16
To nie odpowiada na pytanie. Ale co ważniejsze, jest to bardzo zła rada . Prawie nigdy nie powinieneś używać Result; może to spowodować zakleszczenie! Rozważmy na przykład następujący przebieg pracy: (1) Napisz notatkę o treści „skoś trawnik”. (2) Poczekaj, aż trawnik zostanie skoszony (3) Zjedz kanapkę, (4) Zrób wszystko, co jest napisane w notatce. ”Przy takim przepływie pracy nigdy nie jesz kanapki ani nie kosisz trawnika, ponieważ krok 2 to synchroniczne oczekiwanie na temat czegoś, co będziesz robić w przyszłości . Ale to właśnie tutaj opisujesz przepływ pracy.
Eric Lippert
@EricLippert: Twój przykład nie jest jasny. Czy możesz wyjaśnić, w jaki sposób wynik może wprowadzać zakleszczenia, gdy czekanie nie będzie?
CharithJ
3
Oczekiwanie oznacza zrobienie czegoś, czekając na wynik i to może obejmować wykonanie pracy w celu obliczenia wyniku. Ale synchroniczne oczekiwania nic nie robią podczas czekania, co oznacza, że ​​możesz uniemożliwić wykonanie pracy.
Eric Lippert
1
@EricLippert. Czy będzie to miało ten sam problem? 'Task.Run (() => AccessTheWebAndDouble ()). Wynik;'
CharithJ