# Signales éphémères - Transformation d'atomes en réseau de détection

*Un petit primitive qui transforme le travail simultané en un système d'adaptation coordonné, .*

**"Le patron des signaux éphémèresM SK1**

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

À l'intérieur **[Partie 1](/blog/fire-and-dont-quite-forget-ephemeral-execution)** Nous avons construit une exécution éphémère : **[Partie 2](/blog/ephemeral-execution-library)** Nous avons transformé cette bibliothèque en une bibliothèque réutilisable avec des coordonnateurs, pipelines clés , et l’intégration de DI.

Cet article ajoute une petite fonctionnalité qui change tout: **signals**.

## 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)

L’infrastructure de signalisation vit dans:

| Dossier | But |
|------|---------|
| [Signals.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Signals.cs) | `SignalEvent`, `SignalRetractedEvent`, `SignalPropagation`, `SignalConstraints`, `SignalSink`, `ISignalEmitter`, `AsyncSignalProcessor` |
| [Opération éphémère.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOperation.cs) | Émission de signaux et retrait des opérations |
| [Options éphémères.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOptions.cs) Configuration réactive (`CancelOnSignals`, `DeferOnSignals`, `OnSignal`, `OnSignalRetracted`) |
| [StringPatternMatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/StringPatternMatcher.cs) | GlobM SK1manifestation du modèle pour le filtrage des signaux |
| [SignalDispatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/SignalDispatcher.cs) | Routage de signaux async avec correspondance du modèle (supports `*`, `?`, listes de commasM SK1 ordre déterministe) |
| [Exemples/SignalingHttpClient.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalingHttpClient.cs) | Frais d'échantillonnage -émission de signaux formés pour les appels HTTP
| [Exemples/AdaptiveTranslationService.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/AdaptiveTranslationService.cs) | Limitation du taux d'adaptation avec déferralisation basée sur le signal
| [Exemples/SignalBasedCircuitBreaker.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalBasedCircuitBreaker.cs) | Modèle de lecture des signaux éphémères par l'interrupteur de circuits |
| [Exemples/TelemetrySignalHandler.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/TelemetrySignalHandler.cs) | Traitement de signaux asynchrones avec intégration télémétrique |

[TOC]

---


## Le problème: Atomes isolés

Nos coordonnateurs éphémères sont excellents dans le travail de traitement, mais ils sont isolés.

```csharp
// Translation coordinator has no idea that...
await translationCoordinator.EnqueueAsync(request);

// ...the API just hit a rate limit
// ...another service is experiencing backpressure
// ...a downstream dependency is slow
```

Nous pourrions connecter des dépendances explicites, mais cela crée un couplage. **sensibilisation à l'environnement** - Coordonnateurs capables de percevoir leur environnement sans être directement reliés à celui-ci

Les signaux permettent aux atomes d'exécution de laisser des traces dans leur fenêtre éphémère.

- *"Ce coefficient d'utilisation de l'API est juste-limitéM SK2*
- *" Cet utilisateur a échoué*
- *"A sibling operation is still waiting on the gateway "*

Les coordonnateurs peuvent alors changer leur comportement en fonction des signaux visibles dans leur fenêtre.

---


## La solution: Signals sur les opérations

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

```csharp
public readonly record struct SignalEvent(
    string Signal,
    long OperationId,
    string? Key,
    DateTimeOffset Timestamp,
    SignalPropagation? Propagation = null)
{
    public int Depth => Propagation?.Depth ?? 0;
    public bool WouldCycle(string signal) => Propagation?.Contains(signal) == true;
    public bool Is(string name) => Signal == name;
    public bool StartsWith(string prefix) => Signal.StartsWith(prefix, StringComparison.Ordinal);
}
```

Une opération peut susciter des signaux pendant l’exécution. Ces signaux vivent dans la fenêtre éphémère côte à côté de l’opération. Lorsque l'opération vieillit, les signaux passent avec elle.

Il n’y a pas de courtier en messages, il n’existe pas d’infrastructure distincte, c’est-à-dire seulement des chaînes attachées aux opérations.

Comme les signaux vivent à l’intérieur de la fenêtre éphémère, ils héritent des garanties suivantes :

---


# Les lois sur les signaux éphémères

## Loi 1 - Les signaux ne freinent jamais implicitement l'exécution

Un signal, par lui-mêmeM SK1 n'entraîne pas l'exécution.
Il n’enregistre qu’un fait dans la fenêtre éphémère.
Rien ne fonctionne parce qu'un signal a été émis.

## Droit 2 - OnSignal Is ExplicitM SK2 Local, and Optional

Si un coordonnateur définit un gestionnaire OnSignal, il fonctionne en synchronité lorsqu'un signal est émis - mais seulement parce que le coordonneur a choisi d'y attacher .
Les émetteurs ne savent pas ou s’inquiètent.
Le retrait de tous les gestionnaires laisse le comportement du noyau inchangé.

## Loi 3 - Les signaux sont locaux pour l'atome

Un signal est attaché uniquement à l'atome qui l'a émis.
Aucun signal ne mutait jamais ou n'annotait un autre atome.
Il n'y a pas de bus commun à écrire.

## Loi 4 - Les signaux sont appliquésM SK2 Seulement les faits

L'émission d'un signal ajoute un fait à l'histoire de l'atome
Les signaux ne sont jamais mis à jour ou superscrits.
Les retraits enlèvent uniquement les signaux propres de l’émetteur’.

## Loi 5 - La surface du signal est délimitée et le temps de la transmission est limité

Les signaux ne sont présents que dans la fenêtre éphémère du coordonnateur’
Ils expirent automatiquement à mesure que la fenêtre vieillit.
Rien n’est persisté à moins que vous ne créez explicitement la persistance.

## Droit 6 - Les observateurs lisent, Ils ne disent pas

Lorsqu’un coordonnateur “ vérifie les signaux de la clé KM SK1 il scanne:

les atomes dans sa fenêtre

et les signaux locaux à ces atomes
Les observateurs ne modifient jamais l'état du signal d'un atome’

## Droit 7 - Événement- Le comportement similaire est une coucheM SK3 Pas un élément primaire

Si vous voulez que les signaux conduisent des flux de travail async,, vous devez utiliser:

Système de diffusion des signaux

AsyncSignalProcessor

ou d'autres adapters.

Il s’agit de couches facultatives construites sur les signaux, qui ne font pas partie de leur sémantique

## Droit 8 - Pas de croisementM SK2 Mutation atomique sous toutes circonstances

Aucun atome ne peut modifier les signaux de l'état snapshot,, , ou des métadonnées d'un autre atome..
La coordination se fait par le biais de:

- signals

- de l’inspection

- Les fenêtres

- Politiques

**Non par écrit.**

## Droit 9 - Les points de vue mondiaux sont dérivés, Néanmoins inchangés

Une surface agrégée de signalSink ou d’éclosion- peut présenter une vue combinée des signaux M SK1
mais il est toujours libellé-seulement,ne s'agit jamais d'un texte authentique ,ne peut jamais être écritM SK3

## Loi 10 - Le retrait des manutentionnaires donne le même système de base

Si tous les gestionnaires (OnSignal,dispatchersM SK2processeurs ) sont détachésMSC4
Le système demeure entièrement correct et prévisible.
Les signaux ont toujours un sens parce qu’ils sont des faits.

### La meilleure analogie: empreintes de pied, Pas d'instruction

**Événements** sont comme un appel de téléphone:

> "I'Je vous appele maintenantM SK2 Prenez la parole et réagissez ."

**Signales** sont comme les empreintes de pied dans la neige:

> "J’ai laissé des empreintes de pied . Si vous voulez savoir où j’étais allée - regardez. Si ce n’est pas le cas

C’est la raison pour laquelle les signaux ne brisent jamais, ne bloquent jamais , et n’interagissent jamais avec le flux de commande à moins que vous *Choisissez* pour les interroger.

### Qu'est-ce que cela retient

La distinction est importante parce que les signaux éliminent

| Problème | Événements l'ont-ils survenu MSC2 Signales évitent de le faire MSC3
|---------|:--------------:|:----------------:|
| Coupling | Éditeur MSC2 Abonnés MSC3 Aucune SMC4
| Dépendances en matière de calendrier | Réaction immédiate requise MSC2 Milieu ambiant MSC3 sondage lorsqu'il est prêt SMC4
| Loops de rétroaction | Les gestionnaires peuvent déclencher les gestionnaires | Aucune boucle à moins que cela ne soit explicitement demandé
| Sémantique de l'ordonnance | Questions relatives à l'ordre | Irrelevant M|
| Garanties de livraison | Doit être livré/ manipulé | Aucune livraisonM SK4 seulement l'existence ≥|
| Propagation de l'erreur | Les erreurs du gestionnaire se propagent | Isolée M|
| Risques liés à la réintroduction | courants
| Défaillances causées par la cascade | Défaut d'un gestionnaire MSC2 rupture de chaîne MSC3 Aucune chaîne à briser SMC4

### Comparaison rapide

| Caractéristique | Événements MSC2 Signaux MSC3
|---------|--------|---------|
| Coupling | Strong MSC2publisher MSC3 subscribers MSc4 USC5 Aucune USC6
| Échéancier | Immédiat
| Livraison | Garantie/essayée | Aucune livraisonM SK4 juste existence
| Réaction | Exigé MSC2 Optionnel MSC3
| Charge de données | Souvent lourde MSC2 Méthodes métadonnées à chaînes minces MSC3
| Entreposage | Aucune | Sliding LRUM SK3style window M|
| Durée de vie | Immédiate MSC2 Décompose automatiquement MSC3
| Modes de défaillance | Bon nombre CMS2 Presque aucun CMS3

### La différence dans le code

**Approche des événements (classiqueM SK1**

```csharp
public event Action RateLimited;

try
{
    await CallApiAsync();
}
catch (RateLimitException)
{
    RateLimited?.Invoke(); // Makes someone else act right now
}
```

Problèmes:

- Qui réagit?
- Dans quel ordre?
- Qu'arrive-t-il si deux opérateurs sont en conflit?
- Que se passera-t-il si un gestionnaire lance?
- Qu’arrive-t-il s’il est le mauvais moment?
- Que faire si l’appelant ne veut pas ce comportement'

**Approche de signalisation (éphémèreM SK1**

```csharp
try
{
    await CallApiAsync(ct);
}
catch (RateLimitException ex)
{
    op.Signal($"rate-limit:{ex.RetryAfterMs}ms");
    throw;
}
```

Aucune chose ne se passe ici. Plus tard, ailleurs complètement différentM SK2

```csharp
if (translator.HasSignal("rate-limit"))
{
    await Task.Delay(1000);   // Act when *we* choose
}
```

Notes:

- Aucun couplage direct
- Aucun appel de surprise
- Il n'y a pas de sauts d'exécution
- Il n'y a pas de retour d'appel dans l'enfer
- No cross-thread weirdness
- Il ne réagit que lorsque nous *demander* d'examiner

### Ligne "Aha!"

> **Contrôle du transfert d’événements. Contexte de transfert des signaux.**

Il s’agit de l’ensemble du modèle mental dans une seule phrase.

### Quels sont en réalité les signaux

Selon votre contexte:

| Audience | Définition
|----------|------------|
| **Les penseurs des systèmes** | Un substrat stigmère pour la coordination indirecte |
| **Les ingénieurs** | Données métadonnées légères attachées aux opérations dans une fenêtre de glissage délimitée
| **PL/Nards de compétition** | Un tableau temporel implicite , parallèle à la sémantique de coïncidence limitée
| **Utilisateurs du cadre** | Une surface en état de nettoyage dans un procédé, à l'intérieur d'un processus, d'une surface autonome, d’un processus2 et d’une surface propre que vous pouvez demander à tout moment.

Les signaux sont des traces laissées dans une surface de mémoire partagée. *SIGMERGIQUE* Modèle de coordination - le même système que les souris utilisent , le same système que l’AI précoce utilisait

---


## Comment cela se compare à d'autres approches

### Application Insights / OpenTelemetry

```csharp
using var activity = source.StartActivity("ProcessOrder");
activity?.SetTag("order.id", orderId);
activity?.SetTag("rate.limited", true);
```

**Le meilleur pour**: Traçage réparti entre les services, longsM SK2 stockage de télémétrie à durée déterminée , ID de corrélationMSC4

**Utiliser la télémétrie lorsque**: Vous devez tracer les demandes dans plusieurs services , Mettre à disposition des métriques de stockage pour l’analyse M SK2 ou les intégrer aux outils de surveillance .

**Utiliser des signaux éphémères lorsque**: Vous avez besoin d’une coordination réactive dans le processus de la prise en considération de l’environnement , ou vous ne voulez pas d’infrastructures de télémétrie

### Extensions réactives (Rx)

```csharp
var rateLimits = Observable.FromEventPattern<RateLimitEventArgs>(
    h => api.RateLimitHit += h,
    h => api.RateLimitHit -= h);

rateLimits
    .Throttle(TimeSpan.FromSeconds(1))
    .Subscribe(e => HandleRateLimit(e));
```

**Le meilleur pour**: Traitement complexe des événements, tempsM SK2 opérations fondées sur , combinaison de plusieurs flux d'événements

**Utiliser Rx lorsque**: Vous avez besoin de réponses temporelles complexes.

**Utiliser des signaux éphémères lorsque**: Vous voulez un sondage plus simple - Sensage basé sur les données, Nettoyage automatiqueM SK3 ou intégration avec le suivi de l’exploitationMSC4

### Notifications MediatR

```csharp
public class RateLimitNotification : INotification
{
    public int RetryAfterMs { get; init; }
}

await _mediator.Publish(new RateLimitNotification { RetryAfterMs = 5000 });
```

**Le meilleur pour**: Découplé dans la manipulation des événements de traitement avec plusieurs gestionnaires .

**Utiliser MediatR lorsque**: Vous voulez que plusieurs gestionnaires réagissent à un même événement de façon synchrone.

**Utiliser des signaux éphémères lorsque**: Vous voulez une perception ambiante sans abonnement expliciteM SK1 vous-même - histoire de nettoyage, ou intégration avec l'exécution limitéeMSC4

### Polly Circuit Breaker

```csharp
var circuitBreaker = Policy
    .Handle<HttpRequestException>()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
```

**Le meilleur pour**: Résilience à l'égard des appels individuels avec gestion automatique de l'état

**Utiliser Polly lorsque**: Vous avez besoin d’une résilience à l’appel par -, avec demi-automatisme.

**Utiliser des signaux éphémères lorsque**: Vous voulez une sensibilisation à l'environnement dans de nombreuses opérations , Logique des circuits personnalisés M SK2 ou intégration avec le suivi de l'opération .

**Combiner les deux**: Utilisez Polly à l'intérieur de votre corps de travail , émettez des signaux lorsque les circuits se déplacent

### Tableau de comparaison

| Approche | Automne - Nettoyage | Sensation de l'environnement SMK4 Découplé SMK5 Logique personnalisée MVK6 Intégration SVK7
|----------|:-------------:|:---------------:|:---------:|:------------:|:-----------:|
| OpenTelemetry | ❌ ≥| ≤❌ | =✅ ±| ～❌ ─| Outils externes
| Extensions réactives | ❌ | | 3 | 4 | 5 | 6 | 7 | 8 | 9
| MediatR | ❌ | SMK4 SMK5 FS6 FS7 SFS8 BS9 Manuel PS10
| Disparateur de circuits Polly |
| **Signales éphémères** | ✅ ≥| ≤✅ | ✅ MSC6 MSC7 SMC8 Construit SMC9 dans SMC10

---


## Relever et retracer les signaux

Mise en œuvre des opérations `ISignalEmitter`:

```csharp
public interface ISignalEmitter
{
    // Emit signals
    void Emit(string signal);
    bool EmitCaused(string signal, SignalPropagation? cause);

    // Retract (remove) signals
    bool Retract(string signal);
    int RetractMatching(string pattern);
    bool HasSignal(string signal);

    long OperationId { get; }
    string? Key { get; }
}
```

À l’intérieur de votre corps de travail:

```csharp
await coordinator.ProcessAsync(async (item, op, ct) =>
{
    try
    {
        var result = await CallExternalApiAsync(item, ct);

        if (result.WasCached)
            op.Signal("cache-hit");

        if (result.Duration > TimeSpan.FromSeconds(2))
            op.Signal("slow-response");
    }
    catch (RateLimitException ex)
    {
        op.Signal("rate-limit");
        op.Signal($"rate-limit:{ex.RetryAfterMs}ms");
        throw;
    }
    catch (TimeoutException)
    {
        op.Signal("timeout");
        throw;
    }
});
```

Les signaux sont simplement des chaînes. Utiliser des noms simples (`"rate-limit"`) ou noms structurels (`"rate-limit:5000ms"`).
Les filtres à modèles utilisent la sémantique du globe (`*`, `?`) et listes de commas d'appui (`"error.*,timeout"`). Le parallèle est déterministe et l’allocation `StringPatternMatcher`.

### Fine-Exemple de signal granulé:Calls HTTP

Pour une observabilité très détaillée, vous pouvez émettre des signaux à chaque étape d'une opération . La bibliothèque comprend un échantillon [Signalisation du client Http](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalingHttpClient.cs) qui démontre ce modèle:

```csharp
using Mostlylucid.Helpers.Ephemeral.Examples;

// Inside your work body where you have access to the operation's emitter:
await coordinator.ProcessAsync(async (request, op, ct) =>
{
    var data = await SignalingHttpClient.DownloadWithSignalsAsync(
        httpClient,
        new HttpRequestMessage(HttpMethod.Get, request.Url),
        op,  // ISignalEmitter
        ct);

    // Process the downloaded data...
});
```

Il émet des signaux à chaque étape:

| Signale | Quand MSC2
|--------|------|
| `stage.starting` | Avant le début de la demande |
| `progress:0` | Cote de progrès initiale |
| `stage.request` Requête HTTP envoyée |
| `stage.headers` | Les en-têtes de réponse ont été reçus |
| `stage.reading` | Commençant à lire le corps |
| `progress:XX` | Pourcentage d’avancement
| `stage.completed` | Déchargement terminé |

Vous pouvez ensuite les interroger avec le paramètre correspondant:

```csharp
// Find all stage transitions
var stages = coordinator.GetSignalsByPattern("stage.*");

// Check download progress
var progress = coordinator.GetSignalsByPattern("progress:*");

// Check if any download is still in progress
if (coordinator.HasSignalMatching("stage.reading") &&
    !coordinator.HasSignalMatching("stage.completed"))
{
    // Download in progress
}
```

### Retracter les signaux

Les opérations peuvent aussi retirer leurs propres signaux. Ceci est utile pour les états temporaires:

```csharp
await coordinator.ProcessAsync(async (item, op, ct) =>
{
    // Mark as processing
    op.Emit("processing");

    try
    {
        await ProcessItemAsync(item, ct);

        // Success - retract the processing signal
        op.Retract("processing");
        op.Emit("completed");
    }
    catch (RetryableException)
    {
        // Keep processing signal, add retry info
        op.Emit("retrying");
    }
    catch (Exception)
    {
        // Remove all temporary signals
        op.RetractMatching("processing*");
        op.Emit("failed");
        throw;
    }
});
```

### Événements de rétraction

Tout comme l’émission de signaux, les rétractions peuvent déclencher des appels de rappelM SK1

```csharp
var coordinator = new EphemeralWorkCoordinator<Request>(
    body,
    new EphemeralOptions
    {
        // Sync retraction handler
        OnSignalRetracted = evt =>
        {
            _metrics.DecrementGauge(evt.Signal);
            Console.WriteLine($"Signal {evt.Signal} retracted from op {evt.OperationId}");

            if (evt.WasPatternMatch)
                Console.WriteLine($"  (matched pattern: {evt.Pattern})");
        },

        // Async retraction handler
        OnSignalRetractedAsync = async (evt, ct) =>
        {
            await _telemetry.TrackRetraction(evt.Signal, evt.OperationId, ct);
        }
    });
```

L'article 5 de la Loi sur l'immigration et le statut des réfugiés `SignalRetractedEvent` inclut:

- `Signal` - Nom du signal retiré
- `OperationId` - L’opération qui l’a retirée
- `Key` - La clé de l’opérationM SK1 ( s’il y a une,)
- `Timestamp` - Lorsque la rétraction a eu lieu
- `WasPatternMatch` - Oui si retraité par `RetractMatching`
- `Pattern` - Le modèle utilisé si le modèle correspond à celui de l’autre

### Réel-Exemple mondiale:Récupération des limites de vitesse

```csharp
await coordinator.ProcessAsync(async (request, op, ct) =>
{
    // Check if we already have a rate limit signal
    if (op.HasSignal("rate-limited"))
    {
        // We're in recovery mode
        await Task.Delay(1000, ct);
    }

    try
    {
        var response = await _api.SendAsync(request, ct);

        // Success! Remove any rate limit signal
        if (op.Retract("rate-limited"))
        {
            op.Emit("rate-limit-cleared");
        }
    }
    catch (RateLimitException ex)
    {
        op.Emit("rate-limited");
        op.Emit($"rate-limit:{ex.RetryAfterMs}ms");
        throw;
    }
});
```

---


## Systèmes de détection

Tous les coordonnateurs fournissent une demande de signalisation optimisée:

```csharp
// Check if any recent operation hit a rate limit
if (coordinator.HasSignal("rate-limit"))
{
    await Task.Delay(1000);
}

// Count slow responses in the window
var slowCount = coordinator.CountSignals("slow-response");
if (slowCount > 10)
{
    await ThrottleAsync();
}

// Get signals by pattern
var httpErrors = coordinator.GetSignalsByPattern("http.error.*");

// Get signals since a time
var recentSignals = coordinator.GetSignalsSince(DateTimeOffset.UtcNow.AddMinutes(-1));

// Get signals for a specific key
var userSignals = coordinator.GetSignalsByKey("user-123");
```

---


## Signal-Procédure réactive

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

Les coordonnateurs peuvent réagir automatiquement aux signaux:

```csharp
var coordinator = new EphemeralWorkCoordinator<Request>(
    body,
    new EphemeralOptions
    {
        // Cancel new work if these signals are present
        CancelOnSignals = new HashSet<string> { "system-overload", "circuit-open" },

        // Defer new work while these signals are present
        DeferOnSignals = new HashSet<string> { "rate-limit" },
        MaxDeferAttempts = 10,
        DeferCheckInterval = TimeSpan.FromMilliseconds(100)
    });
```

Lorsqu'un signal est entré `CancelOnSignals` est détecté, de nouveaux articles sont passés à l'écart M SK1 comptabilisé comme ayant échoué ).
Lorsqu'un signal est entré `DeferOnSignals` est détecté, nouveaux articles attendant jusqu’à ce que le signal se clarifieM SK1

---


## Réel-Exemple mondiale: Limitation du taux d'adaptation

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

```csharp
public class AdaptiveTranslationService : IAsyncDisposable
{
    private readonly EphemeralWorkCoordinator<TranslationRequest> _coordinator;
    private readonly ITranslationApi _translationApi;

    public AdaptiveTranslationService(ITranslationApi translationApi)
    {
        _translationApi = translationApi;

        _coordinator = new EphemeralWorkCoordinator<TranslationRequest>(
            ProcessTranslationAsync,
            new EphemeralOptions
            {
                MaxConcurrency = 8,
                MaxTrackedOperations = 100,

                // New work is deferred while any "rate-limit" or "rate-limit:*" signal is present
                DeferOnSignals = new HashSet<string> { "rate-limit", "rate-limit:*" },
                MaxDeferAttempts = 10,
                DeferCheckInterval = TimeSpan.FromMilliseconds(100)
            });
    }

    public async Task TranslateAsync(TranslationRequest request)
    {
        // Optional: extra politeness based on most recent retry-after
        var rateLimitSignals = _coordinator.GetSignalsByPattern("rate-limit:*");
        if (rateLimitSignals.Count > 0)
        {
            var latest = rateLimitSignals
                .OrderByDescending(s => s.Timestamp)
                .First()
                .Signal; // "rate-limit:5000ms"

            if (TryParseRetryAfter(latest, out var delay))
            {
                await Task.Delay(delay);
            }
        }

        await _coordinator.EnqueueAsync(request);
    }

    public static bool TryParseRetryAfter(string signal, out TimeSpan delay)
    {
        delay = default;
        var parts = signal.Split(':', 2);
        if (parts.Length != 2) return false;

        var payload = parts[1].Trim();
        if (!payload.EndsWith("ms", StringComparison.OrdinalIgnoreCase)) return false;

        var numPart = payload[..^2];
        if (!int.TryParse(numPart, out var ms) || ms < 0) return false;

        delay = TimeSpan.FromMilliseconds(ms);
        return true;
    }
}
```

Toutes les instances de ce service s'activent automatiquement lorsque les limites du taux sont atteintes. Aucun état partagéM SK1 Aucun message passible. Je n'ai que lu la fenêtre éphémère .

---


## Réel-Exemple mondiale:CrossM SK2Connaissance du coordonnateur

Plusieurs coordonnateurs peuvent se sentir mutuellement grâce à un `SignalSink`:

```csharp
public class OrderProcessingSystem
{
    private readonly SignalSink _sharedSignals = new(maxCapacity: 1000);

    private readonly EphemeralWorkCoordinator<Order> _orderProcessor;
    private readonly EphemeralWorkCoordinator<PaymentRequest> _paymentProcessor;

    public OrderProcessingSystem()
    {
        var options = new EphemeralOptions { Signals = _sharedSignals };

        _orderProcessor = new EphemeralWorkCoordinator<Order>(
            ProcessOrderAsync, options);

        _paymentProcessor = new EphemeralWorkCoordinator<PaymentRequest>(
            ProcessPaymentAsync, options);
    }

    public async Task ProcessOrderAsync(Order order)
    {
        // Check shared signals for payment gateway issues
        if (_sharedSignals.Detect("gateway-error"))
        {
            await _retryQueue.EnqueueAsync(order);
            return;
        }

        await _orderProcessor.EnqueueAsync(order);
    }
}
```

---


## Surveillance de la santé

```csharp
[HttpGet("/health/detailed")]
public IActionResult GetDetailedHealth()
{
    return Ok(new
    {
        translation = new
        {
            pending = _translationCoordinator.PendingCount,
            active = _translationCoordinator.ActiveCount,
            recentRateLimits = _translationCoordinator.CountSignals("rate-limit"),
            recentTimeouts = _translationCoordinator.CountSignals("timeout"),
            recentSuccess = _translationCoordinator.CountSignals("success"),
            hasErrors = _translationCoordinator.HasSignalMatching("error.*")
        },
        payment = new
        {
            pending = _paymentCoordinator.PendingCount,
            gatewayErrors = _paymentCoordinator.CountSignals("gateway-error"),
            declines = _paymentCoordinator.CountSignals("declined"),
            approvals = _paymentCoordinator.CountSignals("approved")
        }
    });
}
```

Aucune bibliothèque de métrique n'est nécessaire. Demander simplement la fenêtre éphémère.

---


## Réel-Exemple mondialeM SK1Signal-Breakout de circuit basé

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

```csharp
public class SignalBasedCircuitBreaker
{
    private readonly string _failureSignal;
    private readonly int _threshold;
    private readonly TimeSpan _windowSize;

    public SignalBasedCircuitBreaker(
        string failureSignal = "failure",
        int threshold = 5,
        TimeSpan? windowSize = null)
    {
        _failureSignal = failureSignal;
        _threshold = threshold;
        _windowSize = windowSize ?? TimeSpan.FromSeconds(30);
    }

    public bool IsOpen<T>(EphemeralWorkCoordinator<T> coordinator)
    {
        var recentFailures = coordinator.GetSignalsSince(
            DateTimeOffset.UtcNow - _windowSize);

        return recentFailures.Count(s => s.Signal == _failureSignal) >= _threshold;
    }

    public int GetFailureCount<T>(EphemeralWorkCoordinator<T> coordinator)
    {
        var recentFailures = coordinator.GetSignalsSince(
            DateTimeOffset.UtcNow - _windowSize);

        return recentFailures.Count(s => s.Signal == _failureSignal);
    }
}

// Usage
var circuitBreaker = new SignalBasedCircuitBreaker("api-error", threshold: 3);

if (circuitBreaker.IsOpen(_coordinator))
{
    throw new CircuitOpenException("Too many recent API errors");
}

await _coordinator.EnqueueAsync(request);
```

L'interrupteur de circuit n'a pas son propre état - il lit simplement la fenêtre éphémèreM SK1

---


## Constraints de signalisation: Prévention des boucles infinies

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

Lorsque les signaux peuvent causer d’autres signaux, vous risquez des boucles infiniesM SK1 `SignalConstraints` Prévient ce

```csharp
var options = new EphemeralOptions
{
    SignalConstraints = new SignalConstraints
    {
        // Max propagation depth before blocking
        MaxDepth = 10,

        // Prevent A → B → A cycles
        BlockCycles = true,

        // Signals that end propagation chains
        TerminalSignals = new HashSet<string> { "completed", "failed", "resolved" },

        // Signals that emit but don't propagate
        LeafSignals = new HashSet<string> { "logged", "metric" },

        // Callback when a signal is blocked
        OnBlocked = (signal, reason) =>
        {
            _logger.LogWarning("Signal {Signal} blocked: {Reason}",
                signal.Signal, reason);
        }
    }
};
```

### Propagation de signaux

Suivre la causalité avec `EmitCaused`:

```csharp
public void HandleSignal(SignalEvent evt, ISignalEmitter emitter)
{
    if (evt.Is("order-placed"))
    {
        // This signal carries the propagation chain
        // Will be blocked if it would create a cycle
        emitter.EmitCaused("inventory-reserved", evt.Propagation);
    }
}
```

La chaîne de propagation suit le chemin: `order-placed → inventory-reserved → ...`

Si `inventory-reserved` a tenté d'émettre `order-placed`, il serait bloqué (cycle detectedM SK2

---


## L’espace de signal global SignalSink:

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

Pour les signaux qui doivent être visibles à travers les coordonnateurs:

```csharp
public sealed class SignalSink
{
    private readonly ConcurrentQueue<SignalEvent> _window;
    private readonly int _maxCapacity;
    private readonly TimeSpan _maxAge;

    public SignalSink(int maxCapacity = 1000, TimeSpan? maxAge = null);

    // Raise signals
    public void Raise(SignalEvent signal);
    public void Raise(string signal, string? key = null);

    // Sense signals
    public IReadOnlyList<SignalEvent> Sense();
    public IReadOnlyList<SignalEvent> Sense(Func<SignalEvent, bool> predicate);
    public bool Detect(string signalName);
    public bool Detect(Func<SignalEvent, bool> predicate);

    public int Count { get; }
}
```

Usage:

```csharp
// Create a shared sink
var sink = new SignalSink(maxCapacity: 1000, maxAge: TimeSpan.FromMinutes(2));

// Configure coordinators to use it
var options = new EphemeralOptions { Signals = sink };

// Or raise signals directly
sink.Raise("system-maintenance");

// Sense from anywhere
if (sink.Detect("system-maintenance"))
{
    await DeferWorkAsync();
}
```

---


## Pattern Matching with StringPatternMatcher

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

Glob-combinaison de style pour le filtrage des signauxM SK1

```csharp
// Exact match
coordinator.HasSignal("rate-limit");

// Wildcard patterns
coordinator.HasSignalMatching("http.*");           // http.timeout, http.error
coordinator.HasSignalMatching("error.*.critical"); // error.payment.critical
coordinator.HasSignalMatching("user-???-failed");  // user-123-failed

// Comma-separated patterns in CancelOnSignals/DeferOnSignals
new EphemeralOptions
{
    CancelOnSignals = new HashSet<string>
    {
        "system-overload, circuit-open",  // Either pattern
        "error.*"                          // Any error signal
    }
}
```

---


## Conventions sur la désignation des signaux

Maintenir les signaux simples et cohérents:

```csharp
// Good - simple, categorical
op.Signal("success");
op.Signal("failure");
op.Signal("rate-limit");
op.Signal("timeout");
op.Signal("cache-hit");

// Good - structured for parsing
op.Signal("rate-limit:5000ms");
op.Signal("retry:attempt-3");
op.Signal("slow:2500ms");
op.Signal("http.error:429");

// Good - hierarchical for pattern matching
op.Signal("payment.declined");
op.Signal("payment.approved");
op.Signal("payment.gateway-error");

// Avoid - entity identification belongs in Key, not signals
op.Signal("user-123-rate-limited");  // Bad

// Instead
op.Key = "user-123";
op.Signal("rate-limit");
```

---


## Traitement du signal async

Traiteurs de signaux synchrones (`OnSignal`) fonctionner sur l'opération's threadM SK2les maintenir rapidementMSC3 Pour faire IMSSK4O -travail lié~, les signaux de ventilateurs vers un chemin async avec `SignalDispatcher` (compatibilité du modèleM SK1ordre déterministe ) ou `AsyncSignalProcessor`.

### SignalDispatcher fan-out

```csharp
await using var dispatcher = new SignalDispatcher(new EphemeralOptions
{
    MaxConcurrency = Environment.ProcessorCount,
    MaxConcurrencyPerKey = 1  // sequential per signal name by default
});

dispatcher.Register("error.*", evt => _alerts.SendAsync(evt.Signal));
dispatcher.Register("progress:*", evt => _metrics.Record(evt.Signal));

// In coordinator options, keep OnSignal cheap and enqueue
var coordinator = new EphemeralWorkCoordinator<Request>(
    body,
    new EphemeralOptions
    {
        OnSignal = dispatcher.Dispatch
    });
```

Soutien des paramètres `*`, `?`, et listes de commas (`"error.*,timeout"`). Tous les gestionnaires de correspondance fonctionnent dans l'ordre d'enregistrement sur un coordonnateur à clés de fond.

### AsyncSignalProcessor

Pour le traitement async autonome:

```csharp
await using var processor = new AsyncSignalProcessor(
    async (signal, ct) =>
    {
        await _externalService.LogAsync(signal, ct);
    },
    maxConcurrency: 4,
    maxQueueSize: 1000);

// Enqueue signals (returns immediately)
processor.Enqueue(new SignalEvent(
    "rate-limit",
    operationId,
    key,
    DateTimeOffset.UtcNow));
```

### Exemple d'un gestionnaire de signatures de télémétrie

Un exemple complet de la combinaison du traitement des signaux async avec l’intégration télémétrique:

> **Source:** [TelemetrySignalHandler.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/TelemetrySignalHandler.cs)

```csharp
public class TelemetrySignalHandler : IAsyncDisposable
{
    private readonly AsyncSignalProcessor _processor;
    private readonly ITelemetryClient _telemetry;

    public TelemetrySignalHandler(ITelemetryClient telemetry)
    {
        _telemetry = telemetry;
        _processor = new AsyncSignalProcessor(
            HandleSignalAsync,
            maxConcurrency: 8,
            maxQueueSize: 5000);
    }

    // Synchronous entry point - returns immediately
    public bool OnSignal(SignalEvent signal) => _processor.Enqueue(signal);

    private async Task HandleSignalAsync(SignalEvent signal, CancellationToken ct)
    {
        var properties = new Dictionary<string, string>
        {
            ["signal"] = signal.Signal,
            ["operationId"] = signal.OperationId.ToString(),
            ["key"] = signal.Key ?? "none"
        };

        await _telemetry.TrackEventAsync("EphemeralSignal", properties, ct);

        // Categorized tracking based on signal prefix
        if (signal.StartsWith("error"))
            await _telemetry.TrackExceptionAsync(signal.Signal, properties, ct);
        else if (signal.StartsWith("perf"))
            await _telemetry.TrackMetricAsync(signal.Signal, 1, ct);
    }

    // Expose stats for monitoring
    public int QueuedCount => _processor.QueuedCount;
    public long ProcessedCount => _processor.ProcessedCount;
    public long DroppedCount => _processor.DroppedCount;

    public async ValueTask DisposeAsync() => await _processor.DisposeAsync();
}
```

Connectez-le à votre coordonnateur:

```csharp
await using var telemetryHandler = new TelemetrySignalHandler(telemetryClient);

await using var coordinator = new EphemeralWorkCoordinator<Request>(
    ProcessAsync,
    new EphemeralOptions
    {
        OnSignal = signal => telemetryHandler.OnSignal(signal)
    });
```

Le gestionnaire:

- **Ne bloque jamais** le thread d'opération - `OnSignal` retourne immédiatement
- **Catégories des signaux** par préfixe pour différents types de télémétrie
- **Exposer les métriques** (queuedM SK1 processed , dropped) pour surveillance de la santé
- **Mémoire de Bounds** - déverse les signaux les plus anciens si la queue se remplit

---


## Pourquoi cela fonctionne?

Les signaux sont puissants parce qu’ils **d'une durée indéterminée**:

| Biens | Avantage |
|----------|---------|
| **Bounded** | Peut-il ' ne croître sans limite - les vieux signaux vieillissent |
| **Utilisation autonome** | Aucun code de nettoyage n'est nécessaire |
| **Découplé** | Les émetteurs ne connaissent pas les auditeurs ' ne savent pas de ceux-ci
| **Observables** | Tout code peut percevoir l'état actuel |
| **Sous-traitance** | Aucune donnée d'utilisateur - simplement des noms de signaux |
| **Soutien rapide** | OM SK1 détection à courte distance-circuiting |

La fenêtre éphémère est déjà là pour le débugging. Les signaux lui donnent simplement une signification sémantique.

---


## Conclusion

Les signaux transforment les atomes d'exécution isolés en **Réseau de détection**. Chaque coordonnateur peut

- **Émettre** les signaux de ce qu'il a vécu
- **Sens** signals provenant de sa propre histoire ou d'un bassin commun
- **Réaction** par l'entremise de `CancelOnSignals` et `DeferOnSignals`

Aucun courtier de message. Aucun État partagé. Aucun protocole de coordinationM SK2 Seules les opérations avec des métadonnées qui se détruisent naturellement .

Les atomes ne communiquent pas directement entre eux, ils laissent des traces dans une fenêtre éphémère que d'autres peuvent observer.

**Fire... SignaleM SK1 Sens... ForgetMSC3**

---


## 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 2: Établissement d'une bibliothèque réutilisable d'exécution temporaire](/blog/ephemeral-execution-library) - la mise en œuvre complète
- [Stigmergy (Wikipedia)](https://en.wikipedia.org/wiki/Stigmergy) - Coordination indirecte par modification de l'environnement
- [Circuit Breaker Pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker)
- [Extensions réactives](https://github.com/dotnet/reactive)