Skip to content

Defines an awaitable, task-like .NET SlimTask value type suitable for library use that doesn't capture the synchronization context by default. It doesn't require ConfigureAwait(false) everywhere, and it supports ConfigureAwait(true) if you need the context.

License

Notifications You must be signed in to change notification settings

menees/Threading

Repository files navigation

Windows Ubuntu NuGet

Threading

This repo defines some helper types for writing efficient asynchronous code.

SlimTask<TResult>

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.

Examples

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.

Benchmarks

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.

.NET 8

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)

.NET Framework 4.8.1

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)

About

Defines an awaitable, task-like .NET SlimTask value type suitable for library use that doesn't capture the synchronization context by default. It doesn't require ConfigureAwait(false) everywhere, and it supports ConfigureAwait(true) if you need the context.

Topics

Resources

License

Stars

Watchers

Forks