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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Spirantis.Extensions.Threading" Version="10.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Spirantis.Extensions.Threading" Version="10.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Spirantis.Extensions.Threading" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Spirantis.Extensions.Threading --version 10.0.0
                    
#r "nuget: Spirantis.Extensions.Threading, 10.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Spirantis.Extensions.Threading@10.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Spirantis.Extensions.Threading&version=10.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Spirantis.Extensions.Threading&version=10.0.0
                    
Install as a Cake Tool

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 (SemaphoreSlim does 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> — a FunctionExecutionQueue per 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

MIT

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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