Зачем использовать Task над ValueTask в C#?
начиная с C# 7.0 асинхронные методы могут возвращать ValueTask. В объяснении говорится, что он должен использоваться, когда у нас есть кэшированный результат или имитация асинхронности с помощью синхронного кода. Однако я все еще не понимаю, в чем проблема с использованием ValueTask всегда или на самом деле, почему async/await не был построен с типом значения с самого начала. Когда ValueTask не сможет выполнить эту работу?
4 ответов:
С API docs (Курсив мой):
методы могут возвращать экземпляр этого типа значения, когда вполне вероятно, что результат их операций будет доступен синхронно и когда ожидается, что метод будет вызываться так часто, что стоимость выделения нового
Task<TResult>для каждого вызова будет запредельная.есть компромиссы с использованием
ValueTask<TResult>вместоTask<TResult>. Например, в то время какValueTask<TResult>может помочь избежать выделения в случае, когда успешный результат доступен синхронно, он также содержит два поля, тогда как ATask<TResult>в качестве ссылочного типа используется одно поле. Это означает, что вызов метода в конечном итоге возвращает два поля данных вместо одного, что больше данных для копирования. Это также означает, что если метод, который возвращает один из них ждал вasyncметод, государственная машина для этогоasyncметод будет больше из-за необходимости хранить структуру это два поля вместо одной ссылки.далее, для использования, отличного от использования результата асинхронной операции через
await,ValueTask<TResult>может привести к более запутанной модели программирования, которая, в свою очередь, может привести к большему распределению. Например, рассмотрим метод, который может возвращать либоTask<TResult>с кэшированной задачей в качестве общего результата илиValueTask<TResult>. Если потребитель результата хочет использовать его какTask<TResult>, например, для использования с такими методами, какTask.WhenAllиTask.WhenAnyнаValueTask<TResult>сначала нужно будет преобразовать вTask<TResult>используяAsTask, что приводит к выделению, которого можно было бы избежать, если кэшироватьTask<TResult>был использован в первую очередь.в таком виде выбор по умолчанию для любого асинхронного метода должен возвращать
TaskилиTask<TResult>. Только если анализ производительности доказывает, что это стоит того, чтобыValueTask<TResult>вместоTask<TResult>.
однако я до сих пор не понимаю, в чем проблема с использованием ValueTask always
типы структур не являются свободными. Копирование структур, размер которых превышает размер ссылки, может выполняться медленнее, чем копирование ссылки. Хранение структур, которые больше, чем ссылка, занимает больше памяти, чем хранение ссылки. Структуры, размер которых превышает 64 бита, могут не регистрироваться при регистрации ссылки. Преимущества более низкого давления собрания не может превышать расходы.
к проблемам производительности следует подходить с инженерной дисциплиной. Поставьте цели, измерьте свой прогресс в сравнении с целями, а затем решите, как изменить программу, если цели не достигнуты, измеряя по пути, чтобы убедиться, что ваши изменения на самом деле являются улучшениями.
Почему async / await не был построен с типом значения с самого начала.
awaitбыл добавлен в C# послеTask<T>тип уже существовавший. Было бы несколько извращенно изобретать новый тип, когда он уже существовал. Иawaitпрошел через множество итераций дизайна, Прежде чем остановиться на одном, который был поставлен в 2012 году. Идеальный-враг хорошего; лучше отправить решение, которое хорошо работает с существующей инфраструктурой, а затем, если есть спрос пользователей, обеспечить улучшения позже.Я также отмечаю, что новая функция разрешения пользовательских типов является выходом a метод, созданный компилятором, добавляет значительный риск и нагрузку на тестирование. Когда единственное, что вы можете вернуть, - это пустота или задача, команда тестирования не должна рассматривать какой-либо сценарий, в котором возвращается какой-то абсолютно сумасшедший тип. Тестирование компилятора означает выяснение не только того, какие программы люди, вероятно, напишут, но и какие программы возможно писать, потому что мы хотим, чтобы компилятор компилировал все легальные программы, а не только все разумные программы. Это дорогой.
может ли кто-нибудь объяснить, когда ValueTask не сможет выполнить эту работу?
цель вещи-повышение производительности. Это не делает работу, если она не заметно и значительно улучшить производительность. Нет никакой гарантии, что так и будет.
ValueTask<T>не является подмножествомTask<T>, это надмножество.
ValueTask<T>является дискриминируемым объединением A T и ATask<T>, что делает его распределение-бесплатно дляReadAsync<T>чтобы синхронно вернуть значение T, которое у него есть (в отличие от использованияTask.FromResult<T>, который должен выделитьTask<T>экземпляр).ValueTask<T>является ожидаемым, так что большинство потребления экземпляров будет неотличимо от СTask<T>.ValueTask, будучи структурой, позволяет писать асинхронные методы, которые не выделяют память при синхронном запуске без ущерба для согласованности API. Представьте, что у вас есть интерфейс с методом возврата задачи. Каждый класс, реализующий этот интерфейс, должен возвращать задачу, даже если они выполняются синхронно (надеюсь, используя Task.FromResult). Конечно, вы можете иметь 2 разных метода на интерфейсе, синхронный и асинхронный, но для этого требуется 2 разных реализации чтобы избежать "синхронизации по асинхронности"и" асинхронной синхронизации".
таким образом, он позволяет вам написать один метод, который является либо асинхронным, либо синхронным, а не писать один идентичный метод для каждого. Вы можете использовать его в любом месте вы используете
Task<T>но это часто не будет ничего добавлять.Ну, это добавляет одну вещь: он добавляет подразумеваемое обещание вызывающему, что метод на самом деле использует дополнительную функциональность, которая
ValueTask<T>обеспечивает. Я лично предпочитаю выбирать параметры и возвращаемые типы, которые сообщают вызывающему как можно больше. Не возвращайсяIList<T>если перечисление не может предоставить счет; не возвращайтеIEnumerable<T>если это возможно. Ваши потребители не должны искать какую-либо документацию, чтобы знать, какие из ваших методов можно разумно вызвать синхронно, а какие нет.Я не вижу будущих изменений дизайна в качестве убедительного аргумента там. Совсем наоборот: если метод изменяет свою семантику, он должны ломать не строить пока все вызовы к нему не будут обновлены соответствующим образом. Если это считается нежелательным (и поверьте мне, я сочувствую желанию не нарушать сборку), рассмотрите версию интерфейса.
это, по сути, то, для чего нужна сильная типизация.
если некоторые из программистов, разрабатывающих асинхронные методы в вашем магазине, не могут принимать обоснованные решения, было бы полезно назначить старшего наставника каждому из этих менее опытных программистов и иметь еженедельный обзор кода. Если они ошибаются, объясните, почему это должно быть сделано по-другому. Это накладные расходы для старших ребят, но это приведет к тому, что юниоры будут ускоряться гораздо быстрее, чем просто бросать их в глубокий конец и давать им какое-то произвольное правило.
если парень, который написал метод, не знает, можно ли его вызвать синхронно,кто на Земле?!
если у вас так много неопытных программистов, пишущих асинхронные методы, это те те же люди называют их также? Являются ли они квалифицированными, чтобы выяснить для себя, какие из них безопасно называть асинхронными, или они начнут применять аналогичное произвольное правило к тому, как они называют эти вещи?
проблема здесь не в ваших типах возврата, а в том, что программисты помещаются в роли, к которым они не готовы. Что должно было случиться, поэтому я уверен, что это не может быть банальной исправить. Описание этого, конечно, не является решением. Но ищет способ, чтобы проникнуть в проблему прошлое компилятора тоже не является решением.
есть немного изменения в .Net Core 2.1. Начиная с .net core 2.1 ValueTask может представлять не только синхронные завершенные действия, но и асинхронные завершенные действия. Кроме того, мы получаем non-generic
ValueTaskтип.Я оставлю Стивена Туба комментарий что связано с вашим вопросом:
нам все еще нужно формализовать руководство, но я ожидаю, что это будет что-то например, для публичной области API surface area:
задача обеспечивает максимальную удобство использования.
ValueTask предоставляет большинство опций для оптимизации производительности.
- если вы пишете интерфейс / виртуальный метод, который другие будут переопределять, ValueTask является правильным выбором по умолчанию.
- если вы ожидаете, что API будет использоваться на горячих путях, где распределение будет иметь значение, ValueTask-хороший выбор.
- в противном случае, где производительность не является критический, по умолчанию для задачи, так как он обеспечивает лучшее гарантии и удобство использования.
С точки зрения реализации, многие из возвращенные экземпляры ValueTask по-прежнему будут поддерживаться задачей.
функция может использоваться не только в .net core 2.1. Вы сможете использовать его с
Comments