Потребление ValueTask
Когда Использовать ValueTask
Задача
Требуется организовать потребление ValueTask<T>.
Решение
В большинстве случаев все, что необходимо сделать – добавить await:
ValueTask<int> MethodAsync();Также можно выполнить await после выполнения конкурентной операции, как в случае с Task<T>:
…
int value = await MethodAsync();
ValueTask<int> valueTask = MethodAsync();Однако следует помнить, что ValueTask или ValueTask<T> может ожидаться только один раз.
// Другая параллельная работа.
int value = await valueTask;
Чтобы сделать что-то более сложное, преобразуйте ValueTask<T> в Task<T> вызовом AsTask:
Task<int> task = MethodAsync().AsTask();Многократное ожидание Task<T> абсолютно безопасно.
// Другая параллельная работа.
int value = await task;
int anotherValue = await task;
Также возможны другие операции, например, асинхронное ожидание завершения нескольких операций:
Task<int> task1 = MethodAsync().AsTask();Тем не менее для каждого ValueTask<T> можно вызвать AsTask только один раз. Также вы не можете одновременно использовать await и вызвать AsTask для одного ValueTask<T>.
Task<int> task2 = MethodAsync().AsTask();
int[] results = await Task.WhenAll(task1, task2);
Другие свойства ValueTask<T> предназначены для нетривиального использования. Обычно они работают не так, как другие известные свойства. В частности, для ValueTask<T>.Result действуют более жёсткие ограничения, чем для Task<T>.Result. Код, который синхронно получает результат от ValueTask<T>, может вызвать ValueTask<T>.Result или ValueTask<T>. GetAwaiter().GetResult(), но эти компоненты не должны вызываться до завершения ValueTask<T>. Синхронная загрузка результата из Task<T> блокирует вызывающий поток до завершения задачи; ValueTask<T> таких гарантий не даёт. Синхронное получение результатов от ValueTask или ValueTask<T> может быть выполнено только один раз, после завершения ValueTask, и это значение ValueTask уже не может использоваться для ожидания или преобразования в задачу. В противном случае вы получите непредсказуемый результат операции.
Итого
Когда ваш код вызывает метод, возвращающий ValueTask или ValueTask<T>, он должен либо немедленно выполнить await для этого ValueTask, либо немедленно вызвать AsTask для преобразования в Task. Возможно, эта простая рекомендация не исчерпывает все нетривиальные сценарии, но большинству приложений этого будет вполне достаточно.
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 2.