Od C # 7,0 metody asynchroniczne mogą zwracać ValueTask <T>. Wyjaśnienie mówi, że należy go używać, gdy mamy buforowany wynik lub symulujemy asynchroniczność za pomocą kodu synchronicznego. Jednak nadal nie rozumiem, jaki jest problem z używaniem ValueTask zawsze lub w rzeczywistości, dlaczego async / await nie zostało zbudowane z typem wartości od początku. Kiedy ValueTask nie wykona zadania?
c#
asynchronous
Stilgar
źródło
źródło
ValueTask<T>
(pod względem alokacji) niezrealizowaniem operacji, które są faktycznie asynchroniczne (ponieważ w takim przypadkuValueTask<T>
nadal będzie potrzebować alokacji sterty). Jest też kwestiaTask<T>
posiadania innego wsparcia w bibliotekach.Odpowiedzi:
Z dokumentacji API (podkreślenie dodane):
źródło
Task
alokację (która jest obecnie niewielka i tania), ale kosztem zwiększenia istniejącej alokacji wywołującego i podwojenia wielkości zwracanej wartości (wpływającej na alokację rejestrów). Chociaż jest to oczywisty wybór dla scenariusza odczytu buforowanego, stosowanie go domyślnie do wszystkich interfejsów nie jest czymś, co polecam.Task
lubValueTask
może być używany jako synchroniczny typ powrotu (zTask.FromResult
). Ale jest jeszcze wartość (heh) wValueTask
jeśli masz coś, czego oczekiwać , aby być synchroniczne.ReadByteAsync
jest klasycznym przykładem. Wydaje mi się, żeValueTask
został stworzony głównie dla nowych „kanałów” (strumieni bajtów niskiego poziomu), prawdopodobnie używanych również w rdzeniu ASP.NET, gdzie wydajność naprawdę ma znaczenie.Task<T>
jako domyślne. Dzieje się tak tylko dlatego, że większość deweloperów nie jest zaznajomionych z ograniczeniamiValueTask<T>
(w szczególności z regułą „konsumuj tylko raz” i zasadą „bez blokowania”). To powiedziawszy, jeśli wszyscy deweloperzy w twoim zespole są dobrzyValueTask<T>
, to poleciłbym wytyczne dotyczące preferowania na poziomie zespołuValueTask<T>
.Typy struktur nie są darmowe. Kopiowanie struktur, które są większe niż rozmiar odwołania, może być wolniejsze niż kopiowanie odwołania. Przechowywanie struktur, które są większe niż odwołanie, zajmuje więcej pamięci niż przechowywanie odwołania. Struktury, które są większe niż 64 bity, mogą nie zostać zarejestrowane, gdy można zarejestrować odniesienie. Korzyści wynikające z niższego ciśnienia odbioru nie mogą przewyższać kosztów.
Do problemów z wydajnością należy podchodzić z dyscypliną inżynierską. Stawiaj cele, mierz swoje postępy w stosunku do celów, a następnie zdecyduj, jak zmodyfikować program, jeśli cele nie zostaną osiągnięte, mierząc po drodze, aby upewnić się, że zmiany są rzeczywiście ulepszeniami.
await
został dodany do C # długo po tym, jakTask<T>
typ już istniał. Byłoby nieco perwersyjne wymyślić nowy typ, który już istniał. Iawait
przeszedł przez wiele iteracji projektowych, zanim zdecydował się na ten, który został wysłany w 2012 roku. Doskonałość jest wrogiem dobrego; Lepiej jest dostarczyć rozwiązanie, które dobrze współpracuje z istniejącą infrastrukturą, a następnie, jeśli istnieje zapotrzebowanie użytkowników, wprowadzić ulepszenia później.Zwracam również uwagę, że nowa funkcja polegająca na umożliwieniu typom dostarczonym przez użytkownika, aby były wynikiem metody wygenerowanej przez kompilator, zwiększa znaczne ryzyko i obciążenie testowaniem. Kiedy jedyne, co możesz zwrócić, to nieważne lub zadanie, zespół testujący nie musi rozważać żadnego scenariusza, w którym zwracany jest jakiś absolutnie szalony typ. Testowanie kompilatora oznacza ustalenie nie tylko, jakie programy ludzie prawdopodobnie będą pisać, ale jakie programy można napisać, ponieważ chcemy, aby kompilator skompilował wszystkie legalne programy, a nie tylko wszystkie rozsądne programy. To jest drogie.
Celem tego jest poprawa wydajności. Nie ma wykonać zadanie, jeśli nie wymiernie i znacznie poprawić wydajność. Nie ma żadnej gwarancji, że tak się stanie.
źródło
ValueTask<T>
nie jest podzbioremTask<T>
, jest nadzbiorem .Pozwala więc napisać jedną metodę, która jest asynchroniczna lub synchroniczna, zamiast pisać po jednej identycznej metodzie dla każdej. Możesz go użyć w dowolnym miejscu,
Task<T>
ale często nie będzie to nic dodawać.Cóż, dodaje jedną rzecz: dodaje do wywołującego dorozumianą obietnicę, że metoda faktycznie korzysta z dodatkowej funkcjonalności, która
ValueTask<T>
zapewnia. Osobiście wolę wybierać parametry i typy zwracane, które mówią dzwoniącemu jak najwięcej. Nie zwracaj,IList<T>
jeśli wyliczenie nie może zapewnić liczby; nie wracaj,IEnumerable<T>
jeśli to możliwe. Twoi klienci nie powinni musieć szukać dokumentacji, aby wiedzieć, które z metod można rozsądnie wywołać synchronicznie, a które nie.Nie widzę tam przyszłych zmian konstrukcyjnych jako przekonującego argumentu. Wręcz przeciwnie: jeśli metoda zmieni swoją semantykę, powinna przerwać kompilację, dopóki wszystkie jej wywołania nie zostaną odpowiednio zaktualizowane. Jeśli uznasz to za niepożądane (i uwierz mi, sympatyzuję z chęcią nie zepsucia kompilacji), rozważ wersjonowanie interfejsu.
Zasadniczo do tego służy silne pisanie.
Jeśli niektórzy programiści projektujący metody asynchroniczne w Twoim sklepie nie są w stanie podjąć świadomych decyzji, pomocne może być przydzielenie starszego mentora każdemu z tych mniej doświadczonych programistów i cotygodniowy przegląd kodu. Jeśli źle odgadną, wyjaśnij, dlaczego należy to zrobić inaczej. Dla starszych facetów jest to koszt ogólny, ale dzięki temu juniorzy znacznie szybciej będą szybciej niż po prostu rzucić ich na głęboką wodę i dać im jakąś arbitralną zasadę do naśladowania.
Jeśli facet, który napisał tę metodę, nie wie, czy można ją wywołać synchronicznie, kto to robi ?!
Jeśli masz tylu niedoświadczonych programistów piszących metody asynchroniczne, czy ci sami ludzie też je nazywają? Czy mają kwalifikacje do samodzielnego ustalenia, które z nich można bezpiecznie wywołać jako asynchroniczne, czy też zaczną stosować podobnie arbitralną regułę do tego, jak nazywają te rzeczy?
Problemem nie są tutaj typy zwrotów, tylko programiści odgrywający role, na które nie są gotowi. To musiało się wydarzyć z jakiegoś powodu, więc jestem pewien, że naprawienie tego nie może być proste. Opisanie tego z pewnością nie jest rozwiązaniem. Ale szukanie sposobu na przemycenie problemu przez kompilator również nie jest rozwiązaniem.
źródło
ValueTask<T>
, zakładam, że facet, który napisał metodę, zrobił to, ponieważ metoda faktycznie korzysta z funkcji, któraValueTask<T>
dodaje. Nie rozumiem, dlaczego uważasz, że pożądane jest, aby wszystkie metody miały ten sam typ zwrotu. Jaki jest tam cel?object
ponieważ może kiedyś będziesz chciał, aby powróciłyint
zamiaststring
.W .Net Core 2.1 nastąpiły pewne zmiany . Począwszy od .net core 2.1 ValueTask może reprezentować nie tylko ukończone synchroniczne akcje, ale także zakończoną asynchronizację. Dodatkowo otrzymujemy
ValueTask
typ nieogólny .Zostawię komentarz Stephena Toub, który jest związany z Twoim pytaniem:
Funkcję można wykorzystać nie tylko w .net core 2.1. Będziesz mógł go używać z pakietem System.Threading.Tasks.Extensions .
źródło