Когда Использовать ValueTask. Начало
ValueTask<T> используется как возвращаемый тип в ситуациях, в которых чаще всего может быть возвращён синхронный результат, а асинхронное поведение встречается реже. Обычно следует использовать Task<T>, а не ValueTask<T>. Рассматривать использование ValueTask<T> в качестве возвращаемого типа следует только после профилирования, которое показывает, что это приведёт к повышению быстродействия.Простейший способ реализации метода, возвращающего
ValueTask<T>, основан на использовании async и await, как и обычный async-метод. Нередко метод, возвращающий ValueTask<T>, способен немедленно вернуть значение. В таких случаях можно применить оптимизацию для этого сценария с использованием конструктора ValueTask<T>, и передавать управление медленному асинхронному методу только при необходимости:public ValueTask<int> MethodAsync()Возможны ситуации, в которых требуется реализовать метод, возвращающий
{
if (CanBehaveSynchronously)
return new ValueTask<int>(13);
return new ValueTask<int>(SlowMethodAsync());
}
private Task<int> SlowMethodAsync();
ValueTask<T>. Например, при использовании интерфейса IAsyncDisposable, метод DisposeAsync которого возвращает ValueTask. В C# 8.0 и .NET Core 3.0 появилось асинхронное освобождение ресурсов. В BCL появился новый интерфейс IAsyncDisposable, который является асинхронным аналогом IDisposable. Также была введена команда await using — асинхронный аналог using. Таким образом, типы, которые собирались выполнить асинхронную работу при освобождении, получили такую возможность. Возвращаемым типом DisposeAsync является ValueTask, а не Task.В следующем примере показана реализация
IAsyncDisposable, которая выполняет свою логику асинхронного освобождения однократно. При следующих вызовах метод DisposeAsync завершается успешно и синхронно:class MyClass : IAsyncDisposableИспользование:
{
private Func<Task> _disposeLogic;
public ValueTask DisposeAsync()
{
if (_disposeLogic == null)
return default;
// Этот простой пример непотокобезопасный;
// если сразу несколько потоков вызовут DisposeAsync,
// логика может быть выполнена более одного раза.
Func<Task> logic = _disposeLogic;
_disposeLogic = null;
return new ValueTask(logic());
}
}
await using (var myClass = new MyClass())Большинство методов должно возвращать
{
...
} // Здесь вызывается DisposeAsync (с ожиданием)
Task<T>, поскольку при потреблении Task<T> возникает меньше скрытых ловушек, чем при потреблении ValueTask<T>. Чаще всего при реализации интерфейсов, использующих ValueTask или ValueTask<T>, можно просто применять async и await.Окончание следует…
Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Главы 2, 11.