# Établissement d'une bibliothèque réutilisable d'exécution temporaire

<!--category-- ASP.NET, Architecture, Systems Design, Async, DI -->
<datetime class="hidden">2025-12-12T14:00</datetime>

À l'intérieur **[Partie 1: feu et Don't *Bien sûr.* Forget (en anglais seulement)](/blog/fire-and-dont-quite-forget-ephemeral-execution)**, nous avons étudié la théorie qui sous-tend l'exécution éphémère - bounded, privateM SK3 débugging async workflows that remember just enough to be useful and then evaporate

Cet article transforme ce modèle en une bibliothèque réutilisable que vous pouvez déposer dans n'importe quel projet .NET

## NUGET!!!

Cela est maintenant dans le mostlylucid.ephemerals Nuget paquet aussi [plus que 20 les patrons éphémères de la plupart du temps clairs et 'atoms](https://www.nuget.org/packages?q=mostlylucid.ephemeral&includeComputedFrameworks=true&prerel=true&sortby=created-desc).

[![NuGet](https://img.shields.io/nuget/v/mostlylucid.ephemeral.svg)](https://www.nuget.org/packages/mostlylucid.ephemeral)
[![Licence](https://img.shields.io/badge/license-Unlicense-blue.svg)](../../UNLICENSE)

## Les fichiers de source

Le code source complet se trouve dans le [mostlylucid.atoms GitHub repository](https://github.com/scottgal/mostlylucid.atoms)

[TOC]

---


## Avant et après

Voici ce que nous remplaceons :

```csharp
// ❌ Before: Fire-and-forget black hole
_ = Task.Run(() => ProcessAsync(item));
// No visibility. No debugging. No idea if it worked.

// ❌ Or: Blocking everything
await ProcessAsync(item);  // Hope you like waiting...
```

Et ce que nous construisons

```csharp
// ✅ After: Trackable, bounded, debuggable
await coordinator.EnqueueAsync(item);

// Instant visibility
Console.WriteLine($"Pending: {coordinator.PendingCount}");
Console.WriteLine($"Active: {coordinator.ActiveCount}");
Console.WriteLine($"Failed: {coordinator.TotalFailed}");

// Full operation history
var snapshot = coordinator.GetSnapshot();
var failures = coordinator.GetFailed();
```

Exécution asyncratique identique. Observabilité complète. Aucune information sur l'utilisateur n'est conservée .

---


## Démarrage rapide

Le modèle le plus courant - enregistre un coordonnateur dans l’AD et l’injecte

```csharp
// Program.cs
services.AddEphemeralWorkCoordinator<TranslationRequest>(
    async (request, ct) => await TranslateAsync(request, ct),
    new EphemeralOptions { MaxConcurrency = 8 });

// Your service
public class TranslationService(EphemeralWorkCoordinator<TranslationRequest> coordinator)
{
    public async Task TranslateAsync(TranslationRequest request)
    {
        await coordinator.EnqueueAsync(request);
        // Returns immediately - work happens in background
    }

    public object GetStatus() => new
    {
        pending = coordinator.PendingCount,
        active = coordinator.ActiveCount,
        completed = coordinator.TotalCompleted,
        failed = coordinator.TotalFailed
    };
}
```

---


## Quel variable dois-je utiliser?

```text
┌─────────────────────────────────────────────────────────────────┐
│                    DECISION TREE                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Processing a collection once?                                  │
│  └─► EphemeralForEachAsync<T> (ParallelEphemeral.cs)            │
│                                                                 │
│  Need a long-lived queue that accepts items over time?          │
│  └─► EphemeralWorkCoordinator<T>                                │
│                                                                 │
│  Need per-entity ordering (user commands, tenant jobs)?         │
│  └─► EphemeralKeyedWorkCoordinator<TKey, T>                     │
│                                                                 │
│  Need to capture results (fingerprints, summaries)?             │
│  └─► EphemeralResultCoordinator<TInput, TResult>                │
│                                                                 │
│  Need multiple coordinators with different configs?             │
│  └─► IEphemeralCoordinatorFactory<T> (like IHttpClientFactory)  │
│                                                                 │
│  Need dynamic concurrency adjustment at runtime?                │
│  └─► Set EnableDynamicConcurrency = true, call SetMaxConcurrency│
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
```

---


## L'objet de configuration

À partir de [Options éphémères.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOptions.cs):

```csharp
public sealed class EphemeralOptions
{
    // Concurrency control
    public int MaxConcurrency { get; init; } = Environment.ProcessorCount;
    public int MaxConcurrencyPerKey { get; init; } = 1;
    public bool EnableDynamicConcurrency { get; init; } = false;

    // Window management
    public int MaxTrackedOperations { get; init; } = 200;
    public TimeSpan? MaxOperationLifetime { get; init; } = TimeSpan.FromMinutes(5);

    // Fair scheduling (keyed coordinator)
    public bool EnableFairScheduling { get; init; } = false;
    public int FairSchedulingThreshold { get; init; } = 10;

    // Signal-reactive processing
    public IReadOnlySet<string>? CancelOnSignals { get; init; }
    public IReadOnlySet<string>? DeferOnSignals { get; init; }
    public int MaxDeferAttempts { get; init; } = 10;
    public TimeSpan DeferCheckInterval { get; init; } = TimeSpan.FromMilliseconds(100);

    // Signal infrastructure
    public SignalSink? Signals { get; init; }
    public SignalConstraints? SignalConstraints { get; init; }
    public Action<SignalEvent>? OnSignal { get; init; }

    // Async signal handling
    public Func<SignalEvent, CancellationToken, Task>? OnSignalAsync { get; init; }
    public int MaxConcurrentSignalHandlers { get; init; } = 4;
    public int MaxQueuedSignals { get; init; } = 1000;

    // Observability
    public Action<IReadOnlyCollection<EphemeralOperationSnapshot>>? OnSample { get; init; }
}
```

### Principales décisions en matière de conception

- **MaxConcurrency** par défaut au nombre de CPU - sensible pour le CPUM SK1 travail lié. Pour l’I /OMST4 travail limité MST5 l’accroître
- **EnableDynamicConcurrency** permet l'ajustement du temps de démarrage par `SetMaxConcurrency()` - utilise une passerelle personnalisée au lieu de `SemaphoreSlim`.
- **CancelOnSignals/DeferOnSignales** les coordonnateurs signalent -réactifs - ils réagissent à l'état du système ambiant `*`/`?`/listes commÃ©alesM SK1
- **OnSignal** est synchrone; pour le ventilateur async-out usage `SignalDispatcher` ou `AsyncSignalProcessor` à l'intérieur de la gestionnaire.
- **Constraints de signalisation** empêche les boucles de signal infinis avec détection du cycle et limites de profondeur.

---


## Les archives de prises de vue

À partir de [Photos instantanées.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Snapshots.cs):

```csharp
public sealed record EphemeralOperationSnapshot(
    long Id,
    DateTimeOffset Started,
    DateTimeOffset? Completed,
    string? Key,
    bool IsFaulted,
    Exception? Error,
    TimeSpan? Duration,
    IReadOnlyList<string>? Signals = null,
    bool IsPinned = false)
{
    public bool HasSignal(string signal) => Signals?.Contains(signal) == true;
}

// For result-capturing coordinators
public sealed record EphemeralOperationSnapshot<TResult>(
    long Id,
    DateTimeOffset Started,
    DateTimeOffset? Completed,
    string? Key,
    bool IsFaulted,
    Exception? Error,
    TimeSpan? Duration,
    TResult? Result,
    bool HasResult,
    IReadOnlyList<string>? Signals = null,
    bool IsPinned = false);
```

Il s'agit de **seulement les métadonnées**. Avisez ce qui *non pas* ici:

- Aucune charge de travail
- Aucune donnée d'entrée
- Aucun contenu d'utilisateur

Juste assez pour répondre "qu'est-ce qui s'est passé , quand, et a-t-il fonctionné?

---


## Comment cela se compare à d'autres approches

.NET vous donne plusieurs façons d'effectuer des travaux parallèles . Voici' comment la bibliothèque éphémère compare

### Parallèle.ForEachAsync (.NET  6+)

```csharp
await Parallel.ForEachAsync(items,
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    async (item, ct) => await ProcessAsync(item, ct));
```

**Le meilleur pour**: Traitement parallèle simple des collections lorsque vous n’avez pas besoin de visibilité

**Ce qu'il manque**:

- Aucun suivi de l'opération
- Aucune exécution séquentielle de clés per-
- Il n’y a pas de visibilité dans ce qui se passe.

**Utiliser Ephemeral lorsque**: Vous avez besoin de débuggingMSC1observabilité, parM SK3ordonnance des clés(key ordering), ou signal(signal)-traitement réactif(reactive processing).

### Débit de données TPL

```csharp
var block = new ActionBlock<T>(
    async item => await ProcessAsync(item),
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });

foreach (var item in items)
    block.Post(item);

block.Complete();
await block.Completion;
```

**Le meilleur pour**: Pipelines complexes d'acheminement de données avec branchementsM SK1 fusionnements , battage.

**Ce qu'elle fait bien**:

- Composition de pipeline riche (combinaison des blocs de liaisonM SK1
- Établissement de la radiodiffusion
- Capacité limitée avec arrière-pression

**Utiliser TPL Dataflow lorsque**: Vous avez besoin de topologies complexes des pipelines.

**Utiliser Ephemeral lorsque**: Vous avez besoin d'un suivi de l'opérationM SK1 API plus simple , ou signaleMSC3 coordination réactive.

### Système.Threading.Channels

```csharp
var channel = Channel.CreateBounded<T>(100);

// Producer
foreach (var item in items)
    await channel.Writer.WriteAsync(item);
channel.Writer.Complete();

// Consumer (multiple workers)
var workers = Enumerable.Range(0, 4).Select(async _ =>
{
    await foreach (var item in channel.Reader.ReadAllAsync())
        await ProcessAsync(item);
});
await Task.WhenAll(workers);
```

**Le meilleur pour**:Producteur-manipulateurs de consommation où vous contrôlez les deux côtésM SK2

**Ce qu'elle fait bien**:

- Excellente performance
- Retour-pression via des canaux limités
- Séparation des producteurs et des consommateurs

**Utiliser les canaux lorsque**: Vous ' vous construisez une infrastructure personnalisée et avez besoin d’un contrôle maximal

**Utiliser Ephemeral lorsque**: Vous voulez une surveillance et une observabilité des opérations sans plateau de boilerM SK1

### Polly

```csharp
var policy = Policy
    .Handle<HttpRequestException>()
    .WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));

await policy.ExecuteAsync(() => ProcessAsync(item));
```

**Le meilleur pour**: Politiques de résilience (retry, circuit breakerM SK3 timeoutMSC4 pour les opérations individuellesMNK5

**Utiliser Polly lorsque**: Vous avez besoin de résilience à l’égard des appels individuels

**Utiliser Ephemeral lorsque**: Vous avez besoin d’une coordination dans de nombreuses opérations avec une sensibilisation à l’environnement.

**Combiner les deux**: Utilisez Polly à l'intérieur de votre corps de travail éphémère pour chaque

### MassTransit / NServiceBus

**Le meilleur pour**: Message distribué à travers les services avec des queues durables

**Utiliser les bus de message quand**: Le travail doit survivre au renouvellement du procédé , s'étendre à plusieurs services M SK2 ou exiger une livraison garantie .

**Utiliser Ephemeral lorsque**: Le travail se fait dans le cadre d’un procédéM SK1 , ne nécessite pas de durabilité' ne requiert pas de durée de vieMSC4 et vous voulez une surveillance légère

### Tableau de comparaison

| Approche | Boîtement limité | Traçage | Par SMK4 Mot clé SMK5 Systèmes SMC6 Automne SMC7 Nettoyage \ SMC8 Complexité \ |
|----------|:-------:|:--------:|:-------:|:-------:|:-------------:|:----------:|
| `Parallel.ForEachAsync` | ✅ ≥| ≤❌ | ❌ MSC6 MSC7 SMC8 N=/ A+| Sous-
| Flux de données TPL | MSC2 | | 3 | 4 | 5 | 6 | 7 | 8 | 9
| Voies de communications | MSC2 | | 3 | 4 | 5 | 6 | 7 | 8 | 9
| Polly | NM SK2A | ❌ | | 5 | N/A
Services généraux
| MassTransitM SK1NServiceBus | ✅ \| | | ✅ ♫ |
| **Bibliothèque éphémère** | ✅ ≥| ≤✅ | ✅ MSC6 MSC7 msc8 SMC9 SMC10 Moins

---


## EphemeralForEachAsync: The One-Shot Version

À partir de [Parallèle éphémère.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/ParallelEphemeral.cs):

```csharp
// Simple parallel processing with tracking
await items.EphemeralForEachAsync(
    async (item, ct) => await ProcessAsync(item, ct),
    new EphemeralOptions { MaxConcurrency = 8 });

// With keyed execution (per-user sequential)
await commands.EphemeralForEachAsync(
    cmd => cmd.UserId,  // Key selector
    async (cmd, ct) => await ExecuteCommandAsync(cmd, ct),
    new EphemeralOptions
    {
        MaxConcurrency = 32,
        MaxConcurrencyPerKey = 1  // Sequential per user
    });
```

### Pourquoi les pipelines clés sont-elles importantes

Imaginez le traitement des commandes d'utilisateur:

- Utilisateur A envoie les commandes 1, 2, |
- L'utilisateur B envoie les commandes 4, 5, |

Sans clavier,, ils peuvent être exécutés comme:

Avec `MaxConcurrencyPerKey = 1`:

- Les commandes de l'utilisateur A' sont exécutées selon les ordres suivants:
- Les commandes de l'utilisateur B' sont exécutées en ordre:
- Mais A et B peuvent courir en parallèle

Il s'agit de **par -entité séquentielleM SK1 parallèle à l'échelle mondiale** - essentiel pour les systèmes où l’ordre est important au sein d’une entité

---


## Le coordonnateur de travail: Une longue attente-

À partir de [Coordonnateur de travail éphémère.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralWorkCoordinator.cs):

```csharp
await using var coordinator = new EphemeralWorkCoordinator<TranslationRequest>(
    async (request, ct) => await TranslateAsync(request, ct),
    new EphemeralOptions
    {
        MaxConcurrency = 8,
        MaxTrackedOperations = 500,
        EnableDynamicConcurrency = true  // Allow runtime adjustment
    });

// Enqueue items over time
await coordinator.EnqueueAsync(new TranslationRequest("Hello", "es"));

// Check status anytime
Console.WriteLine($"Pending: {coordinator.PendingCount}");
Console.WriteLine($"Active: {coordinator.ActiveCount}");

// Get snapshots
var snapshot = coordinator.GetSnapshot();
var running = coordinator.GetRunning();
var failed = coordinator.GetFailed();
var completed = coordinator.GetCompleted();

// Control flow
coordinator.Pause();   // Stop pulling new work
coordinator.Resume();  // Continue

// Adjust concurrency at runtime (requires EnableDynamicConcurrency)
coordinator.SetMaxConcurrency(16);

// Pin important operations to survive eviction
coordinator.Pin(operationId);
coordinator.Unpin(operationId);
coordinator.Evict(operationId);

// When done
coordinator.Complete();
await coordinator.DrainAsync();
```

### Streams continus avec IAsyncEnumerable

```csharp
await using var coordinator = EphemeralWorkCoordinator<Message>.FromAsyncEnumerable(
    messageStream,  // IAsyncEnumerable<Message>
    async (msg, ct) => await ProcessMessageAsync(msg, ct),
    new EphemeralOptions { MaxConcurrency = 16 });

await coordinator.DrainAsync();
```

---


## Le coordonnateur clé: Per-Entity Pipelines

À partir de [EphemeralKeyedWorkCoordinator.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralKeyedWorkCoordinator.cs):

```csharp
await using var coordinator = new EphemeralKeyedWorkCoordinator<string, Command>(
    cmd => cmd.UserId,  // Key selector
    async (cmd, ct) => await ExecuteCommandAsync(cmd, ct),
    new EphemeralOptions
    {
        MaxConcurrency = 32,
        MaxConcurrencyPerKey = 1,      // Per-user sequential
        EnableFairScheduling = true,   // Prevent hot user starvation
        FairSchedulingThreshold = 10   // Reject if user has 10+ pending
    });

// TryEnqueue returns false if fair scheduling rejects
if (!coordinator.TryEnqueue(hotUserCommand))
{
    await DeferCommandAsync(hotUserCommand);
}

// Per-key visibility
var pendingForUser = coordinator.GetPendingCountForKey("user-123");
var opsForUser = coordinator.GetSnapshotForKey("user-123");
```

---


## Résultat-Coordonnateurs de prise en charge

À partir de [Coordonnateur du résultat éphémère.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralResultCoordinator.cs):

```csharp
await using var coordinator = new EphemeralResultCoordinator<SessionInput, SessionResult>(
    async (input, ct) =>
    {
        var fingerprint = await ComputeFingerprintAsync(input.Events, ct);
        return new SessionResult(fingerprint, input.Events.Length);
    },
    new EphemeralOptions { MaxConcurrency = 16 });

await coordinator.EnqueueAsync(session);
coordinator.Complete();
await coordinator.DrainAsync();

// Get just the results (no metadata)
var results = coordinator.GetResults();

// Get snapshots with results + metadata
var snapshots = coordinator.GetSnapshot();

// Get base snapshots without results (privacy-safe)
var baseSnapshots = coordinator.GetBaseSnapshot();

// Filter by success/failure
var successful = coordinator.GetSuccessful();
var failed = coordinator.GetFailed();
```

---


## Contrôle de la concurrence

À partir de [ConcurrencyGates.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/ConcurrencyGates.cs):

La bibliothèque fournit deux mécanismes de contrôle de la cohérence:

### FixedConcurrencyGate (Default)

- Soutenue par `SemaphoreSlim`
- Rendement optimal de la voie chaude-
- Ne peut pas être ajusté au moment du démarrage

### AdjustableConcurrencyGate

- Mise en œuvre personnalisée avec `Queue<WaiterEntry>`
- Soutien `UpdateLimit()` à l'heure d'exécution
- Activé par le biais `EnableDynamicConcurrency = true`

```csharp
// Dynamic concurrency adjustment
var coordinator = new EphemeralWorkCoordinator<T>(body,
    new EphemeralOptions
    {
        MaxConcurrency = 4,
        EnableDynamicConcurrency = true
    });

// Later, based on system load:
coordinator.SetMaxConcurrency(16);  // Scale up
coordinator.SetMaxConcurrency(2);   // Scale down
```

---


## Le modèle de fabrication: Coordonnateurs désignés

À partir de [DependenceInjection.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/DependencyInjection.cs):

Par exemple : `IHttpClientFactory`, vous pouvez enregistrer des configurations désignées

```csharp
// Registration
services.AddEphemeralWorkCoordinator<TranslationRequest>("fast",
    async (request, ct) => await FastTranslateAsync(request, ct),
    new EphemeralOptions { MaxConcurrency = 32 });

services.AddEphemeralWorkCoordinator<TranslationRequest>("accurate",
    async (request, ct) => await AccurateTranslateAsync(request, ct),
    new EphemeralOptions { MaxConcurrency = 4 });

// Usage
public class TranslationService(IEphemeralCoordinatorFactory<TranslationRequest> factory)
{
    private readonly EphemeralWorkCoordinator<TranslationRequest> _fast =
        factory.CreateCoordinator("fast");
    private readonly EphemeralWorkCoordinator<TranslationRequest> _accurate =
        factory.CreateCoordinator("accurate");
}
```

### Garanties de fabrication

1. **Le même nom = la même instance** - Appels `CreateCoordinator("fast")` deux fois retourne le même coordonnateur
2. **Différents noms = différentes instances** - `"fast"` et `"accurate"` obtenir des coordonnateurs distincts
3. **Création lente** - Les coordonnateurs ne sont créés que lorsque la première demande est faite
4. **Validation de la configuration** - Demander un nom non enregistré provoque une erreur utile

---


## Système d'interprétation des demandes de signal

Tous les coordonnateurs fournissent des méthodes de recherche de signaux optimisées:

```csharp
// Get all signals
var signals = coordinator.GetSignals();

// Filter by key (zero-allocation)
var userSignals = coordinator.GetSignalsByKey("user-123");

// Filter by time range
var recentSignals = coordinator.GetSignalsSince(DateTimeOffset.UtcNow.AddMinutes(-5));
var rangeSignals = coordinator.GetSignalsByTimeRange(from, to);

// Filter by signal name or pattern
var rateSignals = coordinator.GetSignalsByName("rate-limit");
var httpSignals = coordinator.GetSignalsByPattern("http.*");

// Check existence (short-circuits on first match)
if (coordinator.HasSignal("rate-limit"))
    await ThrottleAsync();

if (coordinator.HasSignalMatching("error.*"))
    await AlertAsync();

// Count signals efficiently (no allocation)
var totalSignals = coordinator.CountSignals();
var errorCount = coordinator.CountSignals("error");
var httpCount = coordinator.CountSignalsMatching("http.*");
```

---


## Optimisation de la production

### Production rapide de l'ID

À partir de [EphemeralIdGenerator.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralIdGenerator.cs):

```csharp
internal static class EphemeralIdGenerator
{
    private static long _counter;
    private static readonly long _processStart = Environment.TickCount64;
    private static readonly int _processId = Environment.ProcessId;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static long NextId()
    {
        var counter = Interlocked.Increment(ref _counter);

        // Combine counter with process-unique seed
        Span<byte> buffer = stackalloc byte[24];
        BitConverter.TryWriteBytes(buffer, _processStart);
        BitConverter.TryWriteBytes(buffer.Slice(8), _processId);
        BitConverter.TryWriteBytes(buffer.Slice(16), counter);

        return unchecked((long)XxHash64.HashToUInt64(buffer));
    }
}
```

- **Allocation-free** (utilisations `stackalloc`)
- **Thread-safe** (utilisations `Interlocked.Increment`)
- **Unique dans tous les processus** (comprend l'ID du processusM SK1
- **Non-séquential** (hash diffuse le compteur

### Memory-Safe Long-Lived Operation

Les coordonnateurs ne stockent pas `Task` Références - compteurs simplesM SK1

```csharp
private int _activeTaskCount;
private readonly TaskCompletionSource _drainTcs;

// In ExecuteItemAsync:
finally
{
    // Signal drain when last task completes AND channel iteration is done
    if (Interlocked.Decrement(ref _activeTaskCount) == 0 &&
        Volatile.Read(ref _channelIterationComplete))
    {
        _drainTcs.TrySetResult();
    }
}
```

### Per-Cleanup de clés

Le coordonnateur à clés nettoie automatiquement les semaphores non utilisés par -key

```csharp
private sealed class KeyLock(SemaphoreSlim gate, int maxCount)
{
    public SemaphoreSlim Gate { get; } = gate;
    public int MaxCount { get; } = maxCount;
    public long LastUsedTicks = Environment.TickCount64;
}

// Cleanup runs periodically, removes locks idle > 60 seconds
```

---


## Exemple complète

```csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Named coordinators
builder.Services.AddEphemeralWorkCoordinator<TranslationRequest>("fast",
    async (req, ct) => await FastTranslateAsync(req, ct),
    new EphemeralOptions { MaxConcurrency = 16 });

// Keyed coordinator for per-user commands
builder.Services.AddEphemeralKeyedWorkCoordinator<string, UserCommand>("commands",
    cmd => cmd.UserId,
    sp =>
    {
        var handler = sp.GetRequiredService<ICommandHandler>();
        return async (cmd, ct) => await handler.HandleAsync(cmd, ct);
    },
    new EphemeralOptions
    {
        MaxConcurrency = 32,
        MaxConcurrencyPerKey = 1,
        EnableFairScheduling = true,
        CancelOnSignals = new HashSet<string> { "system-overload" }
    });

var app = builder.Build();
```

```csharp
// Controller
[ApiController]
[Route("api")]
public class WorkController : ControllerBase
{
    private readonly EphemeralWorkCoordinator<TranslationRequest> _translator;
    private readonly EphemeralKeyedWorkCoordinator<string, UserCommand> _commands;

    public WorkController(
        IEphemeralCoordinatorFactory<TranslationRequest> translationFactory,
        IEphemeralKeyedCoordinatorFactory<string, UserCommand> commandFactory)
    {
        _translator = translationFactory.CreateCoordinator("fast");
        _commands = commandFactory.CreateCoordinator("commands");
    }

    [HttpPost("translate")]
    public async Task<IActionResult> Translate([FromBody] TranslationRequest request)
    {
        await _translator.EnqueueAsync(request);
        return Ok(new { pending = _translator.PendingCount });
    }

    [HttpPost("command")]
    public IActionResult SubmitCommand([FromBody] UserCommand command)
    {
        if (!_commands.TryEnqueue(command))
            return StatusCode(429, "Too many pending commands for this user");
        return Ok();
    }

    [HttpGet("status")]
    public IActionResult GetStatus() => Ok(new
    {
        translator = new
        {
            pending = _translator.PendingCount,
            active = _translator.ActiveCount,
            completed = _translator.TotalCompleted,
            failed = _translator.TotalFailed,
            hasRateLimit = _translator.HasSignal("rate-limit")
        },
        commands = new
        {
            pending = _commands.PendingCount,
            active = _commands.ActiveCount,
            errorCount = _commands.CountSignalsMatching("error.*")
        }
    });
}
```

---


## Conclusion

Nous' avons construit une bibliothèque d’exécution éphémère complète avec

1. **`EphemeralForEachAsync`** - UnM SK1traite parallèle avec suivi
2. **`EphemeralWorkCoordinator`** - LongM SK1cartes observables vivantes
3. **`EphemeralKeyedWorkCoordinator`** - Per-exécution séquentielle de l'entité avec calendrier équitable
4. **`EphemeralResultCoordinator`** - Résultat - variante de capture
5. **Méthode de fabrication** - Configurations désignées comme `IHttpClientFactory`
6. **Concurrence dynamique** - Ajustement du parallèle pendant la durée de fonctionnement
7. **Infrastructure de signalisation** - Émission et recherche de signaux internes

Le modèle se situe à un bon endroit:

- Plus observable que `Parallel.ForEachAsync`
- Plus simple que TPL Dataflow
- Plus intégré que les canaux bruts
- Privacy-safe by design

**Fire... et DonM SK1t Quite Forget.**

---


## Liens

- [Partie 1: feu et Don't *Bien sûr.* Forget (en anglais seulement)](/blog/fire-and-dont-quite-forget-ephemeral-execution) - la théorie et le modèle
- [Partie 3: Signales éphémères](/blog/ephemeral-signals) - transformer les atomes en réseau de détection
- [Documentation SemaphoreSlim](https://learn.microsoft.com/en-us/dotnet/api/system.threading.semaphoreslim)
- [Système.Threading.Channels](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels)
- [Débit de données TPL](https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library)
- [Modèle IHttpClientFactory](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests)