Spirantis.Extensions.Threading
10.0.0
dotnet add package Spirantis.Extensions.Threading --version 10.0.0
NuGet\Install-Package Spirantis.Extensions.Threading -Version 10.0.0
<PackageReference Include="Spirantis.Extensions.Threading" Version="10.0.0" />
<PackageVersion Include="Spirantis.Extensions.Threading" Version="10.0.0" />
<PackageReference Include="Spirantis.Extensions.Threading" />
paket add Spirantis.Extensions.Threading --version 10.0.0
#r "nuget: Spirantis.Extensions.Threading, 10.0.0"
#:package Spirantis.Extensions.Threading@10.0.0
#addin nuget:?package=Spirantis.Extensions.Threading&version=10.0.0
#tool nuget:?package=Spirantis.Extensions.Threading&version=10.0.0
Spirantis.Extensions.Threading
A small set of asynchronous coordination primitives for .NET that fill gaps in the built-in BCL types — FIFO-ordered async locking and serial execution of async work, both available globally or partitioned per key.
FifoSemaphore— an async semaphore that grants entry in strict first-in, first-out order (SemaphoreSlimdoes not guarantee ordering).FunctionExecutionQueue— runs async actions one at a time, in enqueue order.KeyedFifoSemaphore— per-key FIFO mutual exclusion; locks on the same key serialize, different keys run concurrently.KeyedFunctionExecutionQueue<TKey>— aFunctionExecutionQueueper key.
Targets .NET 10. MIT licensed.
Installation
dotnet add package Spirantis.Extensions.Threading
Usage
FifoSemaphore
Like SemaphoreSlim, but waiters are released in the exact order they called
WaitAsync. You release a slot manually with Release.
using Spirantis.Extensions.Threading;
using var semaphore = new FifoSemaphore(initialCount: 1);
await semaphore.WaitAsync();
try
{
// critical section — only one caller here at a time, granted in FIFO order
}
finally
{
semaphore.Release();
}
The two-argument constructor sets a maximum count:
using var semaphore = new FifoSemaphore(initialCount: 2, maxCount: 2);
FunctionExecutionQueue
Enqueue async actions and they run strictly one after another, in order — the next
one starts only once the previous has fully completed. Enqueue returns
immediately; the work runs on the thread pool.
var queue = new FunctionExecutionQueue();
queue.OnActionExecuted += () => Console.WriteLine("an action finished");
queue.OnLastAction += () => Console.WriteLine("queue drained");
queue.Enqueue(async () => await SaveAsync(first));
queue.Enqueue(async () => await SaveAsync(second)); // runs after the first completes
A failing action does not stall the queue or skip the following actions.
KeyedFifoSemaphore
Per-key FIFO mutual exclusion. WaitAsync returns an IDisposable; dispose it to
release. The underlying semaphore for a key is created on first use and removed
once its last holder releases it.
var locks = new KeyedFifoSemaphore();
locks.LastProcessForKey += (_, key) => Console.WriteLine($"'{key}' fully released");
// Calls with the same key serialize; different keys run concurrently.
using (await locks.WaitAsync("order-123"))
{
await ProcessAsync("order-123");
}
bool busy = locks.ContainsKey("order-123");
bool idle = locks.IsEmpty;
Disposing the returned handle more than once is safe.
KeyedFunctionExecutionQueue<TKey>
Serial execution per key: actions sharing a key run one at a time in order, while actions under different keys run concurrently. A key's queue is removed once it drains.
var queue = new KeyedFunctionExecutionQueue<string>();
queue.OnActionExecuted += key => Console.WriteLine($"action for '{key}' finished");
queue.OnLastAction += key => Console.WriteLine($"'{key}' drained");
queue.Enqueue("user-1", async () => await HandleAsync(a)); // serialized with...
queue.Enqueue("user-1", async () => await HandleAsync(b)); // ...this one
queue.Enqueue("user-2", async () => await HandleAsync(c)); // runs concurrently
Building & testing
dotnet build src/Spirantis.Extensions.Threading/Spirantis.Extensions.Threading.slnx
dotnet test src/Spirantis.Extensions.Threading.Tests/Spirantis.Extensions.Threading.Tests.csproj
Code is formatted with CSharpier:
csharpier format src/
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0 | 114 | 6/8/2026 |