ValueTask<T>
nie jest podzbiorem Task<T>
, jest nadzbiorem .
ValueTask<T>
jest rozłączną sumą T i a Task<T>
, dzięki czemu jest wolny od alokacji, ReadAsync<T>
aby synchronicznie zwracać dostępną wartość T (w przeciwieństwie do używania Task.FromResult<T>
, które wymaga przydzielenia Task<T>
instancji). ValueTask<T>
jest oczekiwana, więc większość użycia instancji będzie nie do odróżnienia od pliku Task<T>
.
ValueTask, będąc strukturą, umożliwia pisanie metod asynchronicznych, które nie przydzielają pamięci, gdy działają synchronicznie, bez narażania spójności interfejsu API. Wyobraź sobie interfejs z metodą zwracającą Task. Każda klasa implementująca ten interfejs musi zwracać Task, nawet jeśli zdarza się, że są wykonywane synchronicznie (miejmy nadzieję, że używają Task.FromResult). Możesz oczywiście mieć 2 różne metody na interfejsie, synchroniczną i asynchroniczną, ale wymaga to 2 różnych implementacji, aby uniknąć „synchronizacji przez asynchronizację” i „asynchronizacji przez synchronizację”.
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.
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.