ValueTaks vs Tasks
Apresentação
No C#, tanto ValueTask
quanto Task
são tipos usados para lidar com operações assíncronas e concorrentes, mas eles diferem em suas implementações e cenários de uso. Aqui estão as principais diferenças entre eles:
- Alocação de Memória:
ValueTask
: OValueTask
é uma estrutura que é alocada no stack sempre que possível, evitando assim alocações desnecessárias de memória no heap. Isso é benéfico em cenários em que a operação assíncrona frequentemente é concluída de forma síncrona.Task
: OTask
é uma classe que é alocada no heap. Isso pode envolver alocações adicionais de memória, o que pode ser uma desvantagem em termos de desempenho, especialmente para operações que são frequentemente completas de forma síncrona.
- Cenários de Uso:
ValueTask
: É mais adequado para operações assíncronas em que há uma alta probabilidade de que a operação seja concluída de forma síncrona ou em que a alocação de memória adicional seja indesejada. Isso é comum em operações de baixa latência.Task
: É mais apropriado para operações assíncronas que geralmente são completadas de forma assíncrona e em que a alocação de memória adicional não é um grande problema. Isso é comum em operações de I/O de longa duração.
- Número de Awaiters:
ValueTask
: Geralmente é mais eficiente quando há poucos awaiters (código que aguarda a conclusão da operação). Se houver muitos awaiters, pode haver uma alocação de memória adicional para armazenar informações sobre o estado da operação.Task
: É mais eficiente quando há muitos awaiters, porque o overhead de alocação de memória é distribuído entre os awaiters.
- Compatibilidade com APIs Assíncronas:
ValueTask
: Nem todas as APIs assíncronas suportam diretamente o retorno deValueTask
, pois ele é uma estrutura relativamente recente. No entanto, há um esforço contínuo para adicionar suporte aValueTask
em muitas bibliotecas.Task
: A maioria das APIs assíncronas suporta o retorno deTask
, tornando-o mais amplamente aceito.
Geralmente, para escolher entre ValueTask
e Task
, você deve considerar o cenário específico do seu aplicativo. Se a alocação de memória e a eficiência forem preocupações, ValueTask
pode ser uma escolha melhor em cenários de baixa latência. Caso contrário, em operações mais longas ou quando a compatibilidade com bibliotecas existentes for uma prioridade, o uso de Task
pode ser mais apropriado.
Resultado dos tests
Utilizando a biblioteca BenchmarkDotNet podemos comprovar a teoria na práticando, vendo que nos cenários aonde todas as chamdas são asyncrona a Task consegue ser mais performática e no cenários aonde todas as chamadas são sincronas o ValueTask consegue ter praticamente a mesma performace utilizando mesmo memória.
BenchmarkDotNet v0.13.7, Windows 11 (10.0.22621.2215/22H2/2022Update/SunValley2)
Intel Core i7-10750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 7.0.400
[Host]: .NET 7.0.10 (7.0.1023.36312), X64 RyuJIT AVX2
.NET 7.0: .NET 7.0.10 (7.0.1023.36312), X64 RyuJIT AVX2
Job=.NET 7.0 Runtime=.NET 7.0
Method | sync | Mean | Error | StdDev | Rank | Gen0 | Gen1 | Allocated |
---|---|---|---|---|---|---|---|---|
TaskAsync | True | 26.56 ns | 0.567 ns | 1.337 ns | 1 | 0.0178 | - | 112 B |
ValueAsync | True | 30.58 ns | 0.586 ns | 0.548 ns | 2 | 0.0063 | - | 40 B |
TaskAsync | False | 70,778.41 ns | 1,403.415 ns | 2,898.295 ns | 3 | 1.4648 | 0.4883 | 9752 B |
ValueAsync | False | 71,536.87 ns | 1,430.240 ns | 3,078.743 ns | 3 | 1.4648 | 0.4883 | 9680 B |
Outliers:
- TasksTests.TaskAsync: .NET 7.0 -> 5 outliers were removed (33.71 ns..38.12 ns)
- TasksTests.ValueAsync: .NET 7.0 -> 2 outliers were removed (81.97 us, 86.57 us)
Legends:
- sync : Value of the 'sync' parameter
- Mean : Arithmetic mean of all measurements
- Error : Half of 99.9% confidence interval
- StdDev : Standard deviation of all measurements
- Rank : Relative position of current benchmark mean among all benchmarks (Arabic style)
- Gen0 : GC Generation 0 collects per 1000 operations
- Gen1 : GC Generation 1 collects per 1000 operations
- Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
- 1 ns : 1 Nanosecond (0.000000001 sec)