# Öfterliga signaler - Att omvandla atomer till ett känselnätverk

*Ett litet primitiv som omvandlar samarbets till ett koordinerat adaptiv system*

**"Efenomen av tidiga signaler**

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

I **[Del 1](/blog/fire-and-dont-quite-forget-ephemeral-execution)** vi byggde efenerala överdrag - begränsade , privata M SK2 själva - städa asyncka arbetsflöden MSC4 I **[Del 2](/blog/ephemeral-execution-library)** vi omvandlade det till en återanvändbar bibliotek med koordinatorer

Denna artikel lägger till en liten egenskap som ändrar allt **signaler**.

## NUGET!!!

Detta är nu även i det mest uppenbara packaget Nuget [mer än 20](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)
[![Licens](https://img.shields.io/badge/license-Unlicense-blue.svg)](../../UNLICENSE)

## källdates

Den fulla källkoden finns i [mostlylucid.atoms GitHubs arkiv](https://github.com/scottgal/mostlylucid.atoms)

Signalinfrastrukturen bor i:

| Dossier | | | syfte ||
|------|---------|
| [Signaler.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Signals.cs) | `SignalEvent`, `SignalRetractedEvent`, `SignalPropagation`, `SignalConstraints`, `SignalSink`, `ISignalEmitter`, `AsyncSignalProcessor` |
| [EphemeralOperation](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOperation.cs) | Signalutsläpp och dämpning från operationer |
| [ÄphemeralaOptioner.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOptions.cs) | Signal`CancelOnSignals`, `DeferOnSignals`, `OnSignal`, `OnSignalRetracted`) |
| [StringPatternMatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/StringPatternMatcher.cs) | Glob
| [SignalDispatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/SignalDispatcher.cs) | Asynkt signalruttning med mönster matchande ( stöder `*`, `?`, kommalister , deterministisk ordning
| [exempel/Attecknande Httpklient.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalingHttpClient.cs) | Sammafinning - integrerad signalutsläpp för HTTP-sändningar
| [exempel/AdaptiveTranslationService.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/AdaptiveTranslationService.cs) | Adaptiv hastighetsgränsning med signal
| [exempel/SignalBasedCircuitBreaker.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalBasedCircuitBreaker.cs) | Omkretsbrytare läser tidiga signalfenster |
| [exempel/TelemetrySignalHandler.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/TelemetrySignalHandler.cs) | Asynkt signalbehandling med telemetriintegrering |

[TOC]

---


## Problemet : Isolerade atomer

Våra tidiga koordinatorer är bra på att processa arbetet, men de är isolerade. Varje koordinator vet om sina egna aktiviteter, men har ingen aning om vad som händer på andra platser i systemet.

```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
```

Vi skulle kunna koppla upp explicita beroenden, men det skapar kuppling. **omgivningsmedvetenhet** - koordinatorer som kan känna sin omgivning utan att vara direkt anslutna

Signaler låter utövningsatomer lämna spår i sina efemeriska fönster.

- *"Det här API-värdet är rätt*
- *" Den här användaren har misslyckats*
- *"En syskonoperation väntar fortfarande på dörren*

Koordinatorerna kan sedan ändra sitt beteende baserat på de signaler som är synliga i deras fönster.

---


## Lösningen: Signaler på operationer

Från [Signaler.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);
}
```

En operation kan lyfta signaler under uitvoeringen. De signalerna lever i den efenerala venstern bredvid operationen . När operationen åldras ut, går signalerna med

Det är det.

Eftersom signaler lever inuti den efemeriska fönstret, härvt de sina garantier, :, begränsad storlek,,, automatisk åldrande, , och noll livscykel överhuvudtaget.

---


# De tidiga signallagarna

## lag 1 - Signaler Never Implicitly Trigger Execution

Ett signal, i sig självt , orsakar inte utdrag
Den registrerar bara en fakta i den efenerella venstern.
Inget går igång eftersom en signal skickats ut.

## lag 2 - Upptecknen är uppenbar , Lokal , och optionell

Om en koordinator definierar en OnSignal-handlerare, så kör den samtidigt när en signal släpps ut - men bara för att koordinatorn valde att ansluta den
utsändare vet inte eller bryr sig
Att ta bort alla handler lämnar kärnans beteende oföränderligt.

## lag 3 - Signaler är lokala för atomen

Ett signal är kopplat endast till atomen som utstrålade det.
Ingen signal muterar eller summerar någon annan atom
Det finns inga delade skrivbara bussarna.

## Rättsväsendet 4 - Signaler är anhängande-Som fakta

Att skicka en signal lägger till ett faktum i atomens historia
Signaler är aldrig uppdaterade eller överskrivna.
Retraktioner tar bort bara utsändarens egen signaler

## lag 5 - Signalens yta är begränsad och tid

Signaler existerar bara i koordinatorn
De försvinner automatiskt när vensteret åldras.
Inget blir upprätt förrän du explicitt bygger upp upprätthållighet.

## Rättsväsendet 6 - Observatörer läser , De skriver inte

När en koordinator “ser signaler för nyckeln K”, scannar den

atomerna i sitt fönster

och signalerna lokalt till atomerna
Bekymmerna modifierar aldrig en atom

## lag 7 - händelsen- liknande beteende är en ytaM SK3 inte ett primitivt

Om du vill att signaler driver async arbetsflöden, måste du använda::

Signaldispatcher

AsyncSignalProcessor

eller andra adapter.

Dessa är optionella lager byggda ovanpå signaler, inte en del av deras semantik.

## lag 8 - Ingen korsningM SK2 Atommutation under alla omständigheter

Ingen atom kan förändra syntavlan, ,, tillståndet, ,, signalerna,,, eller metadata från en annan atom.
Koordinering sker genom:

- signaler

- känsel

- fönster

- policyr

**Inte genom skrivar.**

## Rättsväsendet 9 - Globala synvinklar är avkomma

En SignalSink eller svamm -samlagd yta kan visa en kombinerad syn på signaler —
men den är alltid läsbar

## lag 10 - Att ta bort handledare ger samma grundsystem

Om alla hållare (OnSignal, utsändare , processorerM SK3 är avkopplade
systemet är fullt korrekt och förutsägbart.
Signaler har fortfarande betydelse eftersom de är fakta.

### Den bästa analogin: Yttermärken , Inte Instruktioner

**Erfarelser** är som ett telefonsamtal

> " jag ' jag ska ringa dig just nu . plocka upp och reagera

**Signaler** är som fotavtryck i snön

> " Jag lämnade fotavtryck

Detta är anledningen till att signaler aldrig bryter, aldrig blockerar och aldrig interagerar med kontrollflödet om du inte *välja* att svara på dem

### Vad detta tar bort

Skillnaden spelar roll eftersom signaler eliminerar

| Problemet | Erfarenheterna har det ♫ ♫ | ♫ Signaler hindrar det ♫
|---------|:--------------:|:----------------:|
| Koppling ♫ ♫ | ♫ Publisher ♫
| Timingsdependenser | | | Omedelbart reagerad || | Omgivningen | МSK3 | undersökning när den är redo
| Feedback-loop | | | Handler kan aktivera handler | МSK2 | Inga loop om inte explicitt frågats |
| Ordering semantics | Order matters ♫ ♫ | ♫ Irrelevant ♫
| Tillförselgarantier | | | måste levereras | / | skött | МSK3 | ingen tillförsel |, | bara existering
| Bredsling av fel | | | Handler fel sprider || | Isolerad |
| Rädslor för återkomst | Vanligt ♫ ♫ | ♫ Otänkbart ♫
| Förluster i kaskad | | | En handledare misslyckas |, | kedjan går sönder | МSK3 | Ingen kedja som går sönder

### Vinnig jämförelse

| Funktion | Erfarenheter | | | Signaler |
|---------|--------|---------|
| Koppling | | | Stabilt | | Belag | МSK3 | Abonnärer |
| Timing | Omedelbart | Omgivningen |
| Tillförsel | | | Gwaranterad | / | försökt | | ingen tillförsel
| Reaktion | | | Erforderlig || | Optionell | МSK3
| Data-payload | | | ofta tunga | МSK2 | små metadata
| lagring | | | Ingen || | Sliding LRU | LRU 3 | Stilfenster |
| livstid | Omedelbart | | | Förrutsnar automatiskt |
| Misslyckande režimer | Många ♫ ♫ | ♫ Nästan ingen ♫

### Skillnaden i kod

**Erfarenhetssätt (klassisktM SK1**

```csharp
public event Action RateLimited;

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

Problemet:

- Vem reagerar?
- I vilken ordning?
- Tänk om två användare konflikterar?
- Tänk om en handledare kastar?
- Tänk om det är fel tid
- Tänk om telefonören inte vill ha det här beteendet

**Signalmetod (ephemeral):**

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

Inget händer här.

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

Märkter:

- Ingen direkt anslutning
- Inga överraskande samtal
- Inga uträttelsesspringar
- Ingen återkoppling i helvetet
- Inget kors-störningstråd
- Bara när vi *fråga* att titta

### Linsen "Aha!"

> **Event-överföring-kontroll. Signalöverföringskontext**

Det är hela den mentala modellen i en mening.

### Vad signaler egentligen är

beroende på din bakgrund:

| Publiken | | | Definitionen ||
|----------|------------|
| **Systemtänkande** | Ett stigmergiskt substans för indirekt koordinering
| **Ingenieurer** | Lightweight metadata som fästes vid operationer i ett begränsat rörande fönster |
| **PL/Konkurentskördor** | En impliserad, temporal svarta tavla, kombinerad med begränsad samtida semantik.
| **Framework-användare** | En in-process, -, ,, själv--, renande yta som du kan bläddra vid varje tid.

Signaler är spår som lämnas i ett gemensamt minne. *stigmergisk* modell av koordinering - samma mönster som använder , samma svarta bordssystem som användes i tidiga AI

---


## Hur det ser ut jämfört med andra metoder

### Application Insights / OpenTelemetry

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

**Den bästa för**: Distribuerad spårning över hela tjänsterna , långvarig M SK2 termisk telemetrins lagring , korrelationsidentifikationer

**Använd telemetri när**: Du behöver spåra begäran över flera tjänster , spara mätinstrument för analysen , eller integrera det med övervakningsverktyg

**Använd tidiga signaler när**: Du behöver i processmässigt omgivningsmedvetenhet

### Reaktiva utlängningar (Rx)

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

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

**Den bästa för**: komplext händelsefördelning , tid, - operationer baserade på , kombinerade flera händelseflöden

**Använd Rx när**: Du behöver komplexa temporära sökningar ( vingande , avbrytande M SK3 kombinerade flöden MSC4

**Använd tidiga signaler när**: Du vill ha en enklare rösting -baserade känseln M SK2 automatisk renning , eller integrering med operationsföljning

### MediatR-avsikter

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

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

**Den bästa för**: Avkopplad i-process handling av händelser med flera skötare

**Använd MediatR när**: Du vill att flera sköterare ska reagera på samma händelse samtidigt

**Använd tidiga signaler när**: Du vill ha omgivningskänning utan explicit subskryption

### Polly-kretsarbrytare

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

**Den bästa för**: Robusthet kring individuella samtal med automatiskt tillståndskontroll

**Använd Polly när**: Du behöver per-uppkopplingsförmåga med automatisk halvans -öppen | / | instängda förvandlar |. |

**Använd tidiga signaler när**: Du vill ha omgivningsmedvetenhet över många operationer , skräddarsydda kretslogik

**Kombinera dem**: Använd Polly inuti din arbetskropp , släpp ut signaler när kretsar triggas

### Vergleichstabellen

| Närhållningssätt | Själva | | - | rengörning ♫ | | Ambient Sensing ♫
|----------|:-------------:|:---------------:|:---------:|:------------:|:-----------:|
| OpenTelemetry
| Reaktiva utlängningar | ❌ ♫ ♫ | ♫
| MediatR ♫ ♫ | ♫
| Polly-kretsbrytare
| **Äphemerala signaler** |  ✅

---


## Att lyfta och dra tillbaka signaler

Operationer implementerar `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; }
}
```

Inuti din arbetskropp:

```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;
    }
});
```

Signaler är bara strängar. Använd enkla namn (`"rate-limit"`) eller strukturerade namn`"rate-limit:5000ms"`).
Pattern-filter använder glob-semantik (`*`, `?`) och stödja kommalister (`"error.*,timeout"`). matchning är deterministisk och alokation `StringPatternMatcher`.

### Fint- Ett färgat signalexempla : HTTP sändningar

För mycket detaljerad observabilitet, kan du skicka ut signaler i varje steg av en operation. Biblioteket består av ett exemplar [SignaliseringHttpClient](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalingHttpClient.cs) som visar detta mönster:

```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...
});
```

Detta släpper ut signaler på varje steg:

| Signal
|--------|------|
| `stage.starting` | Innan begäran börjar |
| `progress:0` | Tillgångspunkt
| `stage.request` | HTTP-frågan skickad |
| `stage.headers` | Response naglarna fick |
| `stage.reading` | Att börja läsa kroppen |
| `progress:XX` | Förcentage av framsteg
| `stage.completed` | ladda ner färdigt |

Du kan sedan fråga dessa med mönster matchande:

```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
}
```

### Att dra tillbaka signaler

Operationer kan också ta bort sina egna signaler.

```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;
    }
});
```

### Motsägelseer

Precis som signalutsläppen

```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);
        }
    });
```

Den `SignalRetractedEvent` inkluderar:

- `Signal` - Den återvunna signalnamen
- `OperationId` - Operationen som tog tillbaka den
- `Key` - Operationen
- `Timestamp` - När spänningen uppstod
- `WasPatternMatch` - Verkligen om tillbakatagen via `RetractMatching`
- `Pattern` - Pattern som användes  om mönstret matchar

### verkligt -World Example: Rate Limit Recovery

```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;
    }
});
```

---


## Sensorsignaler

Alla koordinatorer tillhandahåller optimerade signalsökningar:

```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-Reaktiv bearbetning

Från [ÄphemeralaOptioner.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOptions.cs):

Koordinatorer kan automatiskt reagera på signaler:

```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)
    });
```

När en signal kommer in `CancelOnSignals` är upptäckt, nya punkter är hoppade över ( räknats som fel
När en signal kommer in `DeferOnSignals` är upptäckt, nya punkter vänta tills signalen klarar sig.

---


## Verklig-World Example: Adaptiv hastighetsbegränsning

Från [AdaptivTranslationService.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;
    }
}
```

Varje instans av den här tjänsten avbryter sig automatiskt när hastighetsgränserna är träffade. Inget delat tillståndet . Inga meddelanden går igenom. Bara att läsa den efemerala vensternM SK3

---


## Verklig-World Example: CrossM SK2 Koordinatormedvetenhet

Flera koordinatorer kan känna varandra genom en gemensam `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);
    }
}
```

---


## verkligt -World Example: hälsoövervakning

```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")
        }
    });
}
```

Ingen mätライブryre behövs. Bara fråga i den efemerala venstern.

---


## Verklig -World Example: Signal-Based Circuit Breaking

Från [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);
```

Den kretsbrytare har ingen egen tillstånd. - den läser bara det efemerala oknet.

---


## Signalbegränsningar: Att förhindra oändliga kretsar

Från [Signaler.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Signals.cs):

När signaler kan orsaka andra signaler `SignalConstraints` förhindrar detta:

```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);
        }
    }
};
```

### Signalförökning

spåra orsakssambandet med `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);
    }
}
```

Förökningskedjan följer banan: `order-placed → inventory-reserved → ...`

Om `inventory-reserved` försökte utsläppa `order-placed`, det skulle bli blockerat ( cykel upptäckt

---


## SignalSink: Global Signal Space

Från [Signaler.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Signals.cs):

För signaler som behöver vara synliga över koordinatorer:

```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; }
}
```

Användning:

```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 matchar med StringPatternMatcher

Från [StringPatternMatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/StringPatternMatcher.cs):

Glob-stilmatchning för signalfilterning:

```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
    }
}
```

---


## Signal Naming Konventioner

Håll signalerna enkla och konsekventa:

```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");
```

---


## Async Signalbehandling

Synkroniserade signalbehandlingar (`OnSignal`) köra på operationen ' threaden, — sköta dem snabbt, . Att göra I/ OM SK5 gränslig arbete, , driva ventilatorsignaler in i en async bana med `SignalDispatcher` (möjlig matchning av mönster `AsyncSignalProcessor`.

### SignalDispatcher ventilator

```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
    });
```

Patterns stöd `*`, `?`, och kommalister (`"error.*,timeout"`). Alla matchande handler körs i registrationsordning på en koordinator med bakgrundsklyter ; utsläppen är fortfarande sinkroniserad

### AsyncSignalProcessor

För standalone async-processing:

```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));
```

### TelemetrySignalHandler exempel

Ett komplett exempel på att kombinera async-signalbehandling med telemetriintegration:

> **källa:** [TelemetrisignalHandler.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();
}
```

koppla upp det till din koordinator:

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

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

Handlaren:

- **Aldrig blockerar** operationstråden - `OnSignal` återvänder omedelbart
- **Kategoriserar signaler** med prefix för olika typer av telemetri
- **Exposerar mätetal** (
- **Avtryck i minnet** - släpper ned de äldsta signalerna om köen fyller upp

---


## Varför det här fungerar

Signaler är kraftfulla eftersom de **efemeral**:

| egendom | | | Belöning ||
|----------|---------|
| **Avgränsad** | KanM SK1 inte växa obegränsad - gamla signaler åldras
| **Självt städning** | Ingen städkod behövs |
| **Avkopplade** | utsändare vet inte ' vet inte om lyssnare
| **Observerbara** | Alla koder kan känna den nuvarande tillståndet |
| **Privat** | Inga användardata - bara signalernas namn |
| **Vinnig** | OM SK1 detektion med korta apparater

Det efemerala vensteret finns redan för att lösa problem.

---


## Slutsatsen

Signaler omvandlar isolerade utövningsatomer till **känselnätverk**. Varje koordinator kan

- **Emit** signaler om vad den upplevde
- **Förnuft** signaler från sin egen historia eller ett gemensamt vattenkoll
- **reagera** automatiskt via `CancelOnSignals` och `DeferOnSignals`

Inga meddelandesmakare . Inga gemensamt tillstånd. Inga koordineringsprotokoll. Bara operationer med metadata som helt naturligt bryts ut

Atomerna pratar inte med varandra direkt, de lämnar bara spår i ett efemeriskt fönster som andra kan observera.

**Brand ... Signal... Bemärkelse... FörgettnaM SK3**

---


## Links

- [Del 1: Brand och Don *Ganska* Glöm](/blog/fire-and-dont-quite-forget-ephemeral-execution) - teorin och mönstret
- [Del 2: Att bygga en återanvändbar ephemeral Execution Library](/blog/ephemeral-execution-library) - fulla implementering
- [Stigmergy (Wikipedia)](https://en.wikipedia.org/wiki/Stigmergy) - indirekt koordinering genom att modifiera miljön
- [Sirkelbrytaremönster](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker)
- [Reaktiva Erweiterunger](https://github.com/dotnet/reactive)