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)