Aller au contenu principal

ValkarnSemaphore

ValkarnSemaphore est un sémaphore asynchrone à zéro allocation construit sur ValkarnTask. Il fonctionne comme SemaphoreSlim mais s'intègre nativement avec le pipeline Valkarn Tasks — aucune allocation de Task, aucune pression sur le GC en régime stable.

namespace UnaPartidaMas.Valkarn.Tasks

Chemin rapide (sans contention) : retourne ValkarnTask.CompletedTask immédiatement — zéro allocation. Chemin lent (avec contention) : les nœuds d'attente sont empruntés depuis un pool borné et y sont retournés à la complétion — zéro GC en régime stable.


Constructeur

public ValkarnSemaphore(int initialCount, int maxCount)
ParamètreDescription
initialCountNombre de créneaux disponibles immédiatement. Doit être dans [0, maxCount].
maxCountBorne supérieure du nombre de créneaux. Doit être > 0.

Propriétés

public int CurrentCount { get; } // Créneaux disponibles actuellement (thread-safe)

Méthodes

WaitAsync

public ValkarnTask WaitAsync(CancellationToken ct = default)

Acquiert un créneau de manière asynchrone. Retourne un ValkarnTask complété immédiatement si un créneau est disponible (zéro allocation). Suspend l'appelant jusqu'à ce que Release() soit appelé si aucun créneau n'est disponible. Les waiters sont servis dans l'ordre FIFO.

Lève OperationCanceledException si ct se déclenche pendant l'attente.

Wait

public void Wait(CancellationToken ct = default)

Variante synchrone. Bloque le thread appelant si aucun créneau n'est disponible. À utiliser uniquement lorsque await ne peut pas être utilisé (p. ex. points d'entrée non asynchrones).

Release

public void Release(int releaseCount = 1)

Restitue releaseCount créneaux. Les waiters en attente sont complétés dans l'ordre FIFO, en dehors du verrou interne — les continuations ne s'exécutent jamais avec le verrou acquis.

Lève InvalidOperationException si la libération dépasserait maxCount.

Dispose

public void Dispose()

Annule tous les waiters en attente et libère les ressources. Idempotent.


Utilisation

Exclusion mutuelle (1 créneau)

var sem = new ValkarnSemaphore(initialCount: 1, maxCount: 1);

async ValkarnTask WriteFileAsync(string path, string content, CancellationToken ct)
{
await sem.WaitAsync(ct);
try
{
await File.WriteAllTextAsync(path, content, ct);
}
finally
{
sem.Release();
}
}

Concurrence bornée (N créneaux)

// Autorise jusqu'à 4 requêtes réseau concurrentes.
var sem = new ValkarnSemaphore(initialCount: 4, maxCount: 4);

async ValkarnTask FetchAsync(string url, CancellationToken ct)
{
await sem.WaitAsync(ct);
try
{
await DownloadAsync(url, ct);
}
finally
{
sem.Release();
}
}

Porte producteur / consommateur

// Démarre avec 0 créneau — le consommateur attend jusqu'à ce que le producteur signale.
var gate = new ValkarnSemaphore(initialCount: 0, maxCount: 1);

async ValkarnTask ProducerAsync(CancellationToken ct)
{
await ProduceDataAsync(ct);
gate.Release(); // signaler le consommateur
}

async ValkarnTask ConsumerAsync(CancellationToken ct)
{
await gate.WaitAsync(ct); // attendre le signal
await ConsumeDataAsync(ct);
}

Sécurité des threads

Toutes les opérations sont thread-safe. Release() peut être appelé depuis n'importe quel thread, y compris les threads en arrière-plan. Les complétions sont dispatchées en dehors du verrou interne — une continuation qui réentre dans le sémaphore ne provoquera pas de deadlock.


Comparaison avec SemaphoreSlim

ValkarnSemaphoreSemaphoreSlim
Allocation chemin rapideZéroZéro
Allocation chemin lentNœud en pool, GC zéroAllocation de Task
Intégration avec ValkarnTaskOuiNécessite .AsValkarnTask()
IDisposableOuiOui
Ordre FIFOOuiOui
AnnulationOuiOui