Всем привет! Ну вот и подходят к концу новогодние праздники. Решил потихоньку приподнять лицо из оливье и попробовать, попытаться объяснить описать возможности класса ValueTask (оставляю ссылку на официальную документацию).
Но давайте сначала разберёмся с Task в C#
Task является очень гибким классом и это даёт ряд преимуществ. Например, можно выполнить await несколько раз для любого количества потребителей одновременно. А можно положить его в коллекцию (dictionary) для повторных await в будущем, чтобы использовать его как кеш результатов асинхронных вызовов. Также можно заблокировать выполнение, ожидая завершения Task, если такое понадобится. И также можно написать и применить разнообразные операции над объектами Task (иногда их называют «комбинаторами»), например, «когда любая» (“when any”) для асинхронного ожидания первого завершения из нескольких Task.
Но эта гибкость становится лишней в наиболее часто встречающемся случае: просто вызвать асинхронную операцию и дождаться выполнения задачи:
TResult result = await SomeOperationAsync();UseResult(result);
Здесь не понадобится многократного ожидания выполнения участка кода. Нет необходимости обеспечить конкуррентность ожиданий и выполнения синхронной блокировки или написания комбинаторов. Мы просто ждём выполнения promise асинхронной операции. В конце концов, это так, как мы пишем синхронный код (например, TResult result = SomeOperation();), и это обычным образом переводится на язык async/await.
Более того, у Task есть потенциальная слабая сторона, особенно когда создаётся большое количество его экземпляров, а большая пропускная способность и производительность являются ключевыми требованиями – Task является классом. Это означает, что любая операция, которой понадобился Task, вынуждена создавать и размещать объект в хипе, а чем больше объектов создаётся, тем больше работы для сборщика мусора (GC), и на эту работу расходуются ресурсы, которые мы могли бы потратить на что-то более полезное.
А при чём тут ValueTask?
Всё это потребовало реализации в .NET Core 2.0 нового типа, который доступен в предыдущих версиях .NET в пакете NuGet System.Threading.Tasks.Extensions: ValueTask<TResult>. Он был создан в .NET Core 2.0 как структура, способная обернуть как TResult, так и Task<TResult>. Это означает, что её можно возвращать из async метода, и, если этот метод выполнится синхронно и успешно, никакого объекта в куче размещать не надо: вы можете просто инициализировать эту структуру ValueTask<TResult> значением TResult и вернуть. Только в случае асинхронного выполнения объект Task<TResult> будет размещён, а ValueTask<TResult> обернёт его (чтобы минимизировать размер структуры и оптимизировать случай успешного исполнения, async метод, который завершается с неподдерживаемым исключением, также будет размещать Task<TResult>, так что ValueTask<TResult> так же просто обернёт Task<TResult>, а не будет таскать с собой дополнительное поле для хранения Exception).
Если подытожить, то ValueTask — это структура в .NET, которая используется для представления асинхронной операции. Она является альтернативой Task, но предназначена для более эффективного управления ресурсами в случаях, когда асинхронная операция может завершаться очень быстро, возможно даже синхронно.
Итого, можно следующим образом констатировать особенности использования ValueTask:
1️⃣Повторное ожидание запрещено: вызывать await несколько раз для одного и того же ValueTask нельзя, так как это может привести к неожиданным результатам.
❌ Так делать нельзя:
ValueTask<int> task = GetValueAsync();
int value1 = await task;
int value2 = await task; // Тут будет ошибка2️⃣Конвертация в Task: если требуется передать ValueTask в API, которое ожидает Task, можно вызвать метод .AsTask():
Task<int> task = GetValueAsync().AsTask();3️⃣Когда использовать ValueTask:
✅Операция часто завершается синхронно.
✅Создание объекта Task может быть слишком накладным.
✅Вы пишете библиотеку с высокой производительностью.
4️⃣Когда НЕ использовать ValueTask:
❌Операция всегда асинхронна.
❌Производительность не является критически важной.
❌Приложение не оптимизировано под работу с ValueTask.
Надеюсь, уважаемый читатель, что данная статья полностью объяснила разницу между 2-я классами и теперь не будет сложности в их использовании на практике.
Источники:
Комментариев нет:
Отправить комментарий