ValueTask<T>
это не подмножество Task<T>
, это надмножество .
ValueTask<T>
является дискриминационным объединением T и a Task<T>
, что делает его свободным для выделения, ReadAsync<T>
чтобы синхронно возвращать имеющееся у него значение T (в отличие от использования Task.FromResult<T>
, которому требуется выделить Task<T>
экземпляр). ValueTask<T>
является приемлемым, поэтому большинство случаев использования будет неотличимо от a Task<T>
.
ValueTask, являясь структурой, позволяет писать асинхронные методы, которые не выделяют память при синхронной работе без нарушения согласованности API. Представьте, что у вас есть интерфейс с методом возврата Task. Каждый класс, реализующий этот интерфейс, должен возвращать Задачу, даже если они выполняются синхронно (возможно, с использованием Task.FromResult). Конечно, вы можете иметь 2 разных метода на интерфейсе: синхронный и асинхронный, но для этого требуется 2 разных реализации, чтобы избежать «синхронизации по асинхронности» и «асинхронизации по синхронизации».
Таким образом, он позволяет вам написать один метод, который является либо асинхронным, либо синхронным, вместо того, чтобы писать один метод, идентичный для всех остальных. Вы можете использовать его где угодно, Task<T>
но часто это ничего не добавляет.
Ну, это добавляет одну вещь: это добавляет подразумеваемое обещание вызывающей стороне, что метод фактически использует дополнительные функциональные возможности, которые ValueTask<T>
предоставляют. Я лично предпочитаю выбирать параметры и возвращаемые типы, которые сообщают вызывающей стороне как можно больше. Не возвращайте, IList<T>
если перечисление не может обеспечить счет; не возвращайся, IEnumerable<T>
если сможешь. Ваши потребители не должны искать какую-либо документацию, чтобы знать, какие из ваших методов могут вызываться синхронно, а какие нет.
Я не рассматриваю будущие изменения дизайна как убедительный аргумент. Наоборот: если метод меняет свою семантику, он должен прервать сборку, пока все вызовы к нему не будут обновлены соответствующим образом. Если это считается нежелательным (и, поверьте мне, я сочувствую желанию не нарушать сборку), рассмотрите возможность создания версий интерфейса.
По сути, это то, для чего нужна строгая типизация.
Если некоторые программисты, разрабатывающие асинхронные методы в вашем магазине, не могут принимать обоснованные решения, может быть полезно назначить старшего наставника для каждого из этих менее опытных программистов и проводить еженедельный анализ кода. Если они ошибаются, объясните, почему это должно быть сделано по-другому. Для старших ребят это непосильно, но намного быстрее разогнать юниоров, чем просто бросить их в глубокий конец и дать им какое-то произвольное правило для подражания.
Если парень, который написал метод, не знает, может ли он быть вызван синхронно, то кто на Земле знает ?!
Если у вас так много неопытных программистов, пишущих асинхронные методы, эти же люди их тоже называют? Могут ли они сами определить, какие из них безопасно назвать асинхронными, или же они начнут применять подобное произвольное правило к тому, как они называют эти вещи?
Проблема здесь не в ваших типах возвращаемых данных, а в том, что программистов ставят в роли, к которым они не готовы. Это должно было произойти по какой-то причине, поэтому я уверен, что это не может быть тривиально исправить. Описание этого, конечно, не является решением. Но поиск способа обойти проблему без компилятора также не является решением.
ValueTask<T>
(с точки зрения распределения) отсутствия материализации для операций, которые на самом деле являются асинхронными (потому что в этом случаеValueTask<T>
все равно потребуется распределение кучи). ТакжеTask<T>
существует проблема с другой поддержкой в библиотеках.