Зачем использовать задачу над ValueTask в C#?

начиная с C# 7.0 асинхронные методы могут возвращать ValueTask. В объяснении говорится, что он должен использоваться, когда у нас есть кэшированный результат или имитация асинхронности с помощью синхронного кода. Однако я до сих пор не понимаю, в чем проблема с использованием ValueTask всегда или на самом деле, почему async/await не был построен с типом значения с самого начала. Когда бы ValueTask не сделать работу?

4 ответов


С документы API (Курсив мой):

методы могут возвращать экземпляр этого типа значения, когда вероятно, что результат их операций будет доступен синхронно и когда ожидается, что метод будет вызываться так часто, что стоимость выделения нового Task<TResult> для каждого вызова будет запредельная.

есть компромиссы с использованием ValueTask<TResult> вместо Task<TResult>. Например, в то время как ValueTask<TResult> может помочь избежать выделения в случае, когда успешный результат доступен синхронно, он также содержит два поля, тогда как Task<TResult> в качестве ссылочного типа используется одно поле. Это означает, что вызов метода возвращает два поля данных вместо одного, то есть больше данных для копирования. Это также означает, что если метод, который возвращает один из них ждал в async метод, государственная машина для этого async метод будет больше из-за необходимости хранить структуру это два поля вместо одной ссылки.

далее, для использования, отличного от потребления результата асинхронной операции через await, ValueTask<TResult> может привести к более запутанной модели программирования, что, в свою очередь, может привести к большему распределению. Например, рассмотрим метод, который может возвращать либо Task<TResult> с кэшированной задачей в качестве общего результата или ValueTask<TResult>. Если потребитель результата хочет использовать его как Task<TResult>, например, для использования в методах, как Task.WhenAll и Task.WhenAny, the ValueTask<TResult> сначала нужно будет преобразовать в Task<TResult> используя AsTask, что приводит к выделению, которого можно было бы избежать, если кэшировать Task<TResult> был использован в первую очередь.

в таком виде выбор по умолчанию для любого асинхронного метода должен быть возвращен Task или Task<TResult>. Только если анализ производительности доказывает, что это стоитValueTask<TResult> вместо Task<TResult>.


однако я до сих пор не понимаю, в чем проблема с использованием ValueTask always

типы структур не являются бесплатными. Копирование структур, размер которых превышает размер ссылки, может быть медленнее, чем копирование ссылки. Хранение структур, которые больше, чем ссылка, занимает больше памяти, чем хранение ссылки. Структуры, размер которых превышает 64 бита может не быть enregistered, когда ссылка может быть enregistered. Преимущества более низкого давления собрания не может превышать расходы.

к проблемам производительности следует подходить с инженерной дисциплиной. Сформулируйте цели, измерьте свой прогресс относительно целей, а затем решите, как изменить программу, если цели не достигнуты, измеряя по пути, чтобы убедиться, что ваши изменения на самом деле являются улучшениями.

Почему async / await не был построен с типом значения с самого начала.

await был добавлен в C# после Task<T> тип уже существовавший. Было бы несколько извращенно изобретать новый тип, когда он уже существует. И await прошел через множество итераций дизайна, Прежде чем остановиться на одном, который был поставлен в 2012 году. Идеальный-враг хорошего; лучше отправить решение, которое хорошо работает с существующей инфраструктурой, а затем, если есть спрос пользователя, предоставить улучшения позже.

Я также отмечаю, что новая функция разрешения пользовательских типов быть выходом метод, созданный компилятором, добавляет значительный риск и нагрузку на тестирование. Когда единственное, что вы можете вернуть, - это пустота или задача, команде тестирования не нужно рассматривать сценарий, в котором возвращается какой-то абсолютно сумасшедший тип. Тестирование компилятора означает выяснение не только того, какие программы люди могут написать, но и какие программы возможно писать, потому что мы хотим, чтобы компилятор компилировал все юридические программы, а не только все разумные программы. Это дорогой.

может кто-нибудь объяснить, когда ValueTask не сможет выполнить эту работу?

цель вещи-улучшение производительности. Это не делает работу, если она не заметно и значительно улучшить производительность. Нет никакой гарантии, что это произойдет.


ValueTask<T> не является подмножеством Task<T>, это надмножество.

ValueTask<T> является дискриминируемым объединением T и A Task<T>, что делает его свободным от распределения для ReadAsync<T> для синхронного возврата значения T оно доступно (в отличие от использования Task.FromResult<T>, который должен выделить Task<T> экземпляр). ValueTask<T> ожидаемо, поэтому большинство потребления экземпляров будет неотличимо от Task<T>.

ValueTask, будучи структурой, позволяет писать асинхронные методы, которые не выделяют память при синхронном запуске без ущерба для согласованности API. Представьте, что у вас есть интерфейс с методом возврата задачи. Каждый класс, реализующий этот интерфейс, должен возвращать задачу, даже если они выполняются синхронно (надеюсь, используя задачу.FromResult). Конечно, вы можете иметь 2 разных метода на интерфейсе, синхронный и асинхронный, но для этого требуется 2 разных реализации чтобы избежать "sync over async"и" async over sync".

таким образом, он позволяет писать один метод, который является асинхронным или синхронным, а не писать один идентичный метод для каждого. Вы можете использовать его везде, где используете Task<T> но часто это ничего не добавляет.

Ну, он добавляет одну вещь: он добавляет подразумеваемое обещание вызывающему абоненту, что метод фактически использует дополнительную функциональность, которая ValueTask<T> обеспечивает. Лично я предпочитаю выбирать параметры и возвращаемые типы, которые сообщают вызывающему как можно больше. Не возвращайся!--11--> если перечисление не может предоставить счетчик; не возвращайте IEnumerable<T> если это возможно. Ваши потребители не должны искать документацию, чтобы узнать, какие из ваших методов можно разумно назвать синхронно, а какие нет.

Я не вижу будущих изменений дизайна в качестве убедительного аргумента. Совсем наоборот: если метод меняет свою семантику, он должны ломать не строить пока все вызовы к нему не будут обновлены соответствующим образом. Если это считается нежелательным (и поверьте мне, я сочувствую желанию не нарушать сборку), рассмотрите возможность управления версиями интерфейса.

это по сути то, что строгая типизация для.

если некоторые из программистов, разрабатывающих асинхронные методы в вашем магазине, не могут принимать обоснованные решения, может быть полезно назначить старшего наставника каждому из этих менее опытных программистов и еженедельно просматривать код. Если они ошибаются, объясните, почему это должно быть сделано по-другому. Это накладные расходы для старших парней, но это приведет младших к скорости намного быстрее, чем просто бросить их в глубокий конец и дать им какое-то произвольное правило.

если парень, который написал метод, не знает, можно ли его вызвать синхронно,кто на Земле?!

если у вас так много неопытных программистов, пишущих асинхронные методы, это те те же, кто им звонил? Обладают ли они квалификацией, чтобы выяснить для себя, какие из них безопасны для вызова async, или они начнут применять аналогичное произвольное правило к тому, как они называют эти вещи?

проблема здесь не в ваших типах возврата, а в том, что программисты помещаются в роли, к которым они не готовы. Это должно было произойти по какой-то причине, поэтому я уверен, что это не может быть тривиальным. Описание этого, конечно, не является решением. Но!--38-->ищет способ подкрасться к проблеме past компилятор также не является решением.


есть немного изменения в .Net Core 2.1. Начиная с .net core 2.1 ValueTask может представлять не только синхронные завершенные действия, но и асинхронное завершение. Кроме того, мы получаем non-generic ValueTask тип.

Я оставлю Стивена Туба комментарий что связано с вашим вопросом:

нам все еще нужно формализовать руководство, но я ожидаю, что это будет что-то например, для публичной области поверхности API:

  • Task обеспечивает максимальную удобство использования.

  • ValueTask предоставляет большинство опций для оптимизации производительности.

  • если вы пишете интерфейс / виртуальный метод, который другие будут переопределять, ValueTask является правильным выбором по умолчанию.
  • если вы ожидаете, что API будет использоваться на горячих путях, где распределение будет иметь значение, ValueTask-хороший выбор.
  • в противном случае, где производительность не критический, по умолчанию для задачи, так как он обеспечивает лучше гарантии и удобство использования.

С точки зрения реализации, многие из возвращенных экземпляров ValueTask будут подкреплены задач.

функция может использоваться не только в .net core 2.1. Вы сможете использовать его с