This repo defines some helper types for writing efficient asynchronous code.
SlimTask<TResult>
is an awaitable, task-like value type that does NOT capture the current synchronization context by default.
It's useful to return from async
library methods when results are usually available synchronously (similar to ValueTask<TResult>
) but without having to call ConfigureAwait(false)
everywhere.
Since it doesn't capture the synchronization context by default, it doesn't require ConfigureAwait(false)
. However, it supports ConfigureAwait(true)
if you need the context, e.g., if your code invokes a passed-in lambda that may require the captured context.
int value = await GetValueAsync(42); // No need for .ConfigureAwait(false) with SlimTask
...
static async SlimTask<int> GetValueAsync(int value)
{
int result = value;
if (result % 100 == 0)
{
await Task.Delay(1).ConfigureAwait(false);
}
return result;
}
For more examples, see SlimTaskTests.cs.
The Menees.Threading.Benchmarks project uses BenchmarkDotNet. Its results show that SlimTask<TResult>
performs about the same as ValueTask<TResult>
for timing and exactly the same for allocations.
dotnet run -c Release --framework net8.0 -- --outliers DontRemove --filter *
BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4652/24H2/2024Update/HudsonValley)
Intel Core i7-8086K CPU 4.00GHz (Max: 4.01GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 9.0.304
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|----------------- |---------:|---------:|---------:|-------:|----------:|
| ComputeTask | 655.7 ns | 3.23 ns | 3.02 ns | 0.0544 | 344 B |
| ComputeValueTask | 629.5 ns | 10.42 ns | 9.74 ns | 0.0315 | 200 B |
| ComputeSlimTask | 614.0 ns | 12.26 ns | 16.36 ns | 0.0315 | 200 B |
// * Hints *
Outliers
ComputeTasks.ComputeTask: OutlierMode=DontRemove -> 1 outlier was detected (667.19 ns)
ComputeTasks.ComputeSlimTask: OutlierMode=DontRemove -> 2 outliers were detected (649.21 ns, 673.31 ns)
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|------------- |---------:|---------:|---------:|-------:|----------:|
| UseTask | 25.55 ns | 0.241 ns | 0.226 ns | 0.0229 | 144 B |
| UseValueTask | 17.48 ns | 0.324 ns | 0.303 ns | - | - |
| UseSlimTask | 17.97 ns | 0.206 ns | 0.193 ns | - | - |
// * Hints *
Outliers
SimpleTasks.UseTask: OutlierMode=DontRemove -> 1 outlier was detected (27.50 ns)
SimpleTasks.UseSlimTask: OutlierMode=DontRemove -> 1 outlier was detected (19.81 ns)
dotnet run -c Release --framework net48 -- --outliers DontRemove --filter *
BenchmarkDotNet v0.15.2, Windows 11 (10.0.26100.4652/24H2/2024Update/HudsonValley)
Intel Core i7-8086K CPU 4.00GHz (Max: 4.01GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
[Host] : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
DefaultJob : .NET Framework 4.8.1 (4.8.9310.0), X64 RyuJIT VectorSize=256
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|----------------- |---------:|----------:|----------:|-------:|----------:|
| ComputeTask | 2.522 us | 0.0096 us | 0.0090 us | 0.2136 | 1.32 KB |
| ComputeValueTask | 2.407 us | 0.0383 us | 0.0359 us | 0.1869 | 1.17 KB |
| ComputeSlimTask | 2.406 us | 0.0413 us | 0.0387 us | 0.1869 | 1.17 KB |
// * Hints *
Outliers
ComputeTasks.ComputeValueTask: OutlierMode=DontRemove -> 2 outliers were detected (2.33 us, 2.50 us)
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|------------- |---------:|--------:|--------:|-------:|----------:|
| UseTask | 108.3 ns | 2.10 ns | 1.96 ns | 0.0254 | 160 B |
| UseValueTask | 119.6 ns | 1.62 ns | 1.52 ns | - | - |
| UseSlimTask | 114.7 ns | 1.43 ns | 1.34 ns | - | - |
// * Hints *
Outliers
SimpleTasks.UseTask: OutlierMode=DontRemove -> 1 outlier was detected (114.32 ns)