Столкнулся с тем, что, как выяснилось, не всем очевидно для чего нужен ValueTask и когда его стоит использовать. Что ж 🤨
ValueTask - это структура-обертка, которую стоит использовать вместо Task в тех случаях, когда результат может быть получен синхронно, что позволит избежать лишней аллокации объекта Task в куче.
Однако ValueTask не должен использоваться в качестве возвращаемого значения во всех под ряд методах, т.к. это может привести к проблемам с производительностью.
НЕ используйте ValueTask, если выполняются хотябы одно условие:
❌ Метод всегда выполняется асинхронно.
❌ Метод объявлен как async (всегда будет все равно создаваться AsyncStateMachine, поэтому ValueTask будет бессмысленен).
❌ Метод редко вызывается — выигрыш от избежания аллокации Task незначителен.
❌ Метод явялется публичным API библиотеки.
❌ Используется .Result, .GetAwaiter().GetResult(), .AsTask() или повторный await.
public async ValueTask GetUserAsync(Guid id)
{
var result = await dbContext.Users.FirstOrDefaultAsync(u => u.Id == id);
return result;
}
Используйте ValueTask, если выполняются все условия:
✅ Результат может быть получен синхронно (например, из кэша).
✅ Метод не использует async/await.
✅ Метод вызывается часто и критичен по производительности, где аллокации Task значимы.
✅ Нет необходимости повторно ожидать ValueTask.
✅ Вызов Task-версии метода означает ненужную аллокацию, которую ValueTask может избежать.
public ValueTask GetUserAsync(Guid id)
{
if (_cache.TryGetValue(id, out var user))
{
return ValueTask.FromResult(user);
}
return new ValueTask(LoadFromDatabaseAsync(id));
}
private async Task LoadFromDatabaseAsync(Guid id)
{
var result = await dbContext.Users.FirstOrDefaultAsync(u => u.Id == id);
return result;
}
Когда метод возвращает ValueTask, возможны 2 сценария:
1) Если результат уже есть, метод возвращает ValueTask.FromResult(). Результат хранится внутри структуры, лишних аллокаций нет, await завершится сразу.
2) Если нужно дождаться асинхронной операции, ValueTask оборачивает обычный Task, и await работает как обычно. Аллокация есть — но только в этом случае.
Таким образом, не стоит навешивать везде ValueTask. Если метод вызывается десятки тысяч раз в секунду, тогда действительно ValueTask может существенно снизить нагрузку на GC и улучшить производительность 👥.
ValueTask - это структура-обертка, которую стоит использовать вместо Task в тех случаях, когда результат может быть получен синхронно, что позволит избежать лишней аллокации объекта Task в куче.
Однако ValueTask не должен использоваться в качестве возвращаемого значения во всех под ряд методах, т.к. это может привести к проблемам с производительностью.
НЕ используйте ValueTask, если выполняются хотябы одно условие:
❌ Метод всегда выполняется асинхронно.
❌ Метод объявлен как async (всегда будет все равно создаваться AsyncStateMachine, поэтому ValueTask будет бессмысленен).
❌ Метод редко вызывается — выигрыш от избежания аллокации Task незначителен.
❌ Метод явялется публичным API библиотеки.
❌ Используется .Result, .GetAwaiter().GetResult(), .AsTask() или повторный await.
public async ValueTask GetUserAsync(Guid id)
{
var result = await dbContext.Users.FirstOrDefaultAsync(u => u.Id == id);
return result;
}
Используйте ValueTask, если выполняются все условия:
✅ Результат может быть получен синхронно (например, из кэша).
✅ Метод не использует async/await.
✅ Метод вызывается часто и критичен по производительности, где аллокации Task значимы.
✅ Нет необходимости повторно ожидать ValueTask.
✅ Вызов Task-версии метода означает ненужную аллокацию, которую ValueTask может избежать.
public ValueTask GetUserAsync(Guid id)
{
if (_cache.TryGetValue(id, out var user))
{
return ValueTask.FromResult(user);
}
return new ValueTask(LoadFromDatabaseAsync(id));
}
private async Task LoadFromDatabaseAsync(Guid id)
{
var result = await dbContext.Users.FirstOrDefaultAsync(u => u.Id == id);
return result;
}
Когда метод возвращает ValueTask, возможны 2 сценария:
1) Если результат уже есть, метод возвращает ValueTask.FromResult(). Результат хранится внутри структуры, лишних аллокаций нет, await завершится сразу.
2) Если нужно дождаться асинхронной операции, ValueTask оборачивает обычный Task, и await работает как обычно. Аллокация есть — но только в этом случае.
Таким образом, не стоит навешивать везде ValueTask. Если метод вызывается десятки тысяч раз в секунду, тогда действительно ValueTask может существенно снизить нагрузку на GC и улучшить производительность 👥.