# Ephemere Signale - Atome in ein Sensing Network verwandeln

*Ein winziges Primitiv, das gleichzeitige Arbeit in ein koordiniertes, adaptives System verwandelt.*

**"Das ephemere Signalmuster"**

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

Im **[Teil 1](/blog/fire-and-dont-quite-forget-ephemeral-execution)** Wir bauten ephemere Ausführung - begrenzt, private, selbstreinigende async Workflows. In **[Teil 2](/blog/ephemeral-execution-library)** Wir haben daraus eine wiederverwendbare Bibliothek mit Koordinatoren, Schlüsselleitungen und DI-Integration gemacht.

Dieser Artikel fügt ein kleines Feature hinzu, das alles verändert: **Signale**.

## NUGET!!!

Dies ist jetzt in der meistlucid.ephemerals Nuget-Paket auch [mehr als 20 meistlucid.ephemerale Muster und 'Atome'](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)
[![Lizenz](https://img.shields.io/badge/license-Unlicense-blue.svg)](../../UNLICENSE)

## Quelldateien

Der vollständige Quellcode ist in der [meistlucid.atoms GitHub-Repository](https://github.com/scottgal/mostlylucid.atoms)

Die Signalinfrastruktur lebt in:

Datei Zweck
|------|---------|
| [Signale.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Signals.cs) | `SignalEvent`, `SignalRetractedEvent`, `SignalPropagation`, `SignalConstraints`, `SignalSink`, `ISignalEmitter`, `AsyncSignalProcessor` |
| [EphemeralOperation.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOperation.cs) Signal-Emission und -Retraktion von Operationen
| [EphemeralOptions.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOptions.cs) Signal-reaktive Konfiguration (`CancelOnSignals`, `DeferOnSignals`, `OnSignal`, `OnSignalRetracted`) |
| [StringMusterMatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/StringPatternMatcher.cs) Das Muster im Glob-Stil passt sich für die Signalfilterung an.
| [SignalDispatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/SignalDispatcher.cs) Das Async-Signal-Routing mit Muster-Matching (unterstützt `*`, `?`, Kommalisten, deterministische Reihenfolge)
| [Beispiele/SignalingHttpClient.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalingHttpClient.cs) Proben feinkörnige Signalemission für HTTP-Aufrufe
| [Beispiele/AdaptiveTranslationService.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/AdaptiveTranslationService.cs) Anpassende Geschwindigkeitsbegrenzung mit signalbasiertem Deferral
| [Beispiele/SignalBasedCircuitBreaker.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalBasedCircuitBreaker.cs) Schaltungsbrecher, der ephemeres Signalfenster liest
| [Beispiele/TelemetrieSignalHandler.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/TelemetrySignalHandler.cs) Die Verarbeitung von Async-Signalen mit Telemetrie-Integration

[TOC]

---


## Das Problem: Isolierte Atome

Unsere ephemeren Koordinatoren sind sehr gut in der Verarbeitung von Arbeit, aber sie sind isoliert. Jeder Koordinator weiß über seine eigenen Operationen, aber hat kein Bewusstsein für das, was anderswo im System geschieht.

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

Wir könnten explizite Abhängigkeiten verdrahten, aber das schafft Kopplung. Was wir wollen, ist **Umgebungsbewusstsein** - Koordinatoren, die ihre Umwelt spüren können, ohne direkt miteinander verbunden zu sein.

Signale lassen Ausführungsatome Spuren in ihrem ephemeren Fenster hinterlassen. Diese Spuren verhalten sich wie kurzlebige Fakten:

- *"Diese API nur rate-limited"*
- *"Dieser Benutzer hat 3 mal versagt"*
- *"Eine Geschwisteroperation wartet immer noch auf dem Gateway"*

Koordinatoren können dann ihr Verhalten ändern, basierend auf den Signalen, die in ihrem Fenster sichtbar sind. Das ist Umgebungsbewusstsein ohne Abhängigkeiten.

---


## Die Lösung: Signale für den Betrieb

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

Eine Operation kann Signale während der Ausführung anheben. Diese Signale leben im ephemeren Fenster neben der Operation. Wenn die Operation aus altert, gehen die Signale mit ihm.

Kein Nachrichtenmakler, keine separate Infrastruktur, nur Strings, die an Operationen gebunden sind.

Weil Signale innerhalb des ephemeren Fensters leben, erben sie ihre Garantien: begrenzte Größe, automatisches Altern und Null-Lebenszyklus über Kopf.

---


# Die ephemeren Signalgesetze

## Gesetz 1 - Signale führen niemals unplizit die Ausführung aus

Ein Signal allein verursacht keine Hinrichtung.
Es zeichnet nur eine Tatsache im ephemeren Fenster auf.
Nichts läuft, weil ein Signal gesendet wurde.

## Gesetz 2 - OnSignal ist explizit, lokal und optional

Wenn ein Koordinator einen OnSignal-Handler definiert, läuft er synchron, wenn ein Signal ausgesendet wird - aber nur, weil der Koordinator ihn angehängt hat.
Emitter wissen es nicht oder kümmern sich nicht.
Das Entfernen aller Handler lässt das Kernverhalten unverändert.

## Gesetz 3 - Signale sind im Atom lokal

Ein Signal wird nur an das Atom/die Operation angeschlossen, die es emittiert hat.
Kein Signal mutiert oder kommentiert ein anderes Atom.
Es gibt keinen gemeinsamen beschreibbaren Bus.

## Gesetz 4 - Signale sind nur Fakten

Das Senden eines Signals hängt eine Tatsache an die Geschichte des Atoms an.
Signale werden nie aktualisiert oder überschrieben.
Retraktionen entfernen nur die Signale des Emitters.

## Gesetz 5 - Die Signaloberfläche ist gesperrt und zeitlich begrenzt

Signale existieren nur innerhalb des ephemeren Fensters des Koordinators.
Sie verfallen automatisch, wenn das Fenster aus altert.
Nichts ist beharrlich, es sei denn, man baut ausdrücklich Beharrlichkeit auf.

## Gesetz 6 - Beobachter lesen, sie schreiben nicht

Wenn ein Koordinator die Signale auf Schlüssel K. prüft, scannt er:

die Atome in ihrem Fenster

und die Signale lokal zu diesen Atomen
Beobachter ändern niemals den Signalzustand eines Atoms.

## Gesetz 7 - Ereignisähnliches Verhalten ist eine Ebene, keine Primitive

Wenn Signale async-Workflows antreiben sollen, müssen Sie Folgendes verwenden:

SignalDispatcher

AssyncSignalProzessor

oder andere Adapter.

Dies sind optionale Schichten, die auf Signalen aufgebaut sind, nicht Teil ihrer Semantik.

## Gesetz 8 - Keine Kreuzatommutation unter Umständen

Kein Atom darf den Snapshot, Zustand, Signale oder Metadaten eines anderen Atoms verändern.
Die Koordinierung erfolgt durch:

- Signale

- Sensorik

- Fenster

- Politikbereiche

**Nicht durch Briefe.**

## Gesetz 9 - Weltanschauungen werden abgelenkt, nie veränderbar

Eine SignalSink- oder Schwarmaggregierte Oberfläche kann eine kombinierte Ansicht von Signalen zeigen —
aber es ist immer nur lesen, nie maßgeblich, nie beschreibbar.

## Gesetz 10 - Entfernen von Handlern bringt das gleiche Kernsystem

Wenn alle Handler (OnSignal, Dispatcher, Prozessoren) gelöst sind,
das System bleibt vollständig korrekt und vorhersehbar.
Signale haben immer noch Bedeutung, weil sie Fakten sind, keine Auslöser.

### Die beste Analogie: Fußabdrücke, keine Anweisungen

**Veranstaltungen** sind wie ein Anruf:

> "Ich rufe dich an, geh ran und reagiere."

**Signale** sind wie Fußabdrücke im Schnee:

> "Ich habe Fußabdrücke hinterlassen. Wenn du wissen willst, wo ich hingegangen bin - schau, wenn es dir egal ist - ignoriere es."

Aus diesem Grund Signale nie brechen, nie blockieren, und nie mit Steuerungsfluss interagieren, es sei denn, Sie *wählen* um sie zu befragen.

### Was dies entfernt

Die Unterscheidung ist wichtig, weil Signale eliminieren:

Probleme, die sich ergeben, haben Signale, die es vermeiden
|---------|:--------------:|:----------------:|
Publisher → Abonnenten
Timing-Abhängigkeiten. Sofortige Reaktion erforderlich. Ambient, Umfrage, wenn bereit.
Feedback-Schleifen Handler können Handler auslösen Keine Schleifen, es sei denn, explizit gefragt
Beauftragen von Semantiken Beauftragen Beauftragen Unrelevant Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen Beauftragen
Lieferung garantiert muss geliefert / behandelt werden Keine Lieferung, nur Existenz
Fehler-Verbreitung; Fehler-Verbreitung; Fehler-Verbreitung; Fehler-Verbreitung; Fehler-Verbreitung; Fehler-Verbreitung; Fehler-Verbreitung;
Gefahren für die Wiederentspannbarkeit Häufig Unmöglich
Ein Handler versagt, Kette bricht Keine Kette zu brechen

### Schneller Vergleich

Ereignis-Feature, Signale,
|---------|--------|---------|
Starke Koppelung (Veröffentlicher → Abonnenten)
Timing Sofortiges Ambient
Keine Lieferung, nur Existenz
Reagieren erforderlich Fakultativ
• Daten-Payload • Häufig schwer • Kleine String-Metadaten •
Nicht-Speichern-Fenster im LRU-Stil
Lebenslanges, sofortiges Abscheiden automatisch
Fehler Modi Viele Fast keine

### Der Unterschied im Code

**Veranstaltungskonzept (klassisch):**

```csharp
public event Action RateLimited;

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

Probleme:

- Wer reagiert?
- In welcher Reihenfolge?
- Was, wenn zwei Handler kollidieren?
- Was, wenn ein Handler wirft?
- Was, wenn es die falsche Zeit ist?
- Was, wenn der Anrufer dieses Verhalten nicht will?

**Signalanflug (ephemer):**

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

Hier passiert nichts. Später, irgendwo ganz anders:

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

Anmerkungen:

- Keine direkte Verbindung
- Keine Überraschung Anrufe
- Keine Hinrichtungssprünge
- Keine Rückruf-Hölle
- Keine Kreuzgewinde-Wurzel
- reagiert nur, wenn wir *fragen* zu suchen

### Die "Aha!"-Linie

> **Ereignisübertragungskontrolle. Signalübertragungskontext.**

Das ist das gesamte mentale Modell in einem Satz.

### Was wirklich Signale sind

Je nach Hintergrund:

Begriffsbestimmung des Publikums
|----------|------------|
| **Denker der Systeme** Ein stigmatisiertes Substrat für die indirekte Koordination
| **Ingenieure** Leichte Metadaten, die an Operationen in einem begrenzten Schiebefenster angehängt werden
| **PL/Konkurrenz-Nerds** Eine implizite, zeitliche Tafel gepaart mit begrenzter Konkurrenzsemantik
| **Rahmennutzer** Eine im Prozess befindliche, selbstreinigende Zustandsoberfläche, die Sie jederzeit abfragen können

Signale sind Spuren, die in einer gemeinsamen, begrenzten Speicheroberfläche hinterlassen werden. Jeder kann sie ansehen. Niemand ist verpflichtet zu reagieren. *Stigmatisierung* Modell der Koordination - die gleiche Ameisen verwenden, die gleiche Tafelsysteme in frühen KI verwendet, und die gleichen modernen CRDT Klatsch-Netzwerke weisen auf.

---


## Wie dies mit anderen Ansätzen verglichen wird

### Anwendungs-Insights / OpenTelemetrie

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

**Das Beste für**: Verteilte Rückverfolgung über Dienstleistungen, Langzeit-Telemetrie-Speicher, Korrelations-IDs.

**Telemetrie verwenden, wenn**: Sie müssen Anfragen über mehrere Dienste verfolgen, Metriken zur Analyse speichern oder mit Monitoring-Tools integrieren.

**Verwende ephemere Signale, wenn**: Sie brauchen In-Prozess-Ambient-Bewusstsein, reaktive Koordination, oder wollen keine Telemetrie-Infrastruktur.

### Reaktive Erweiterungen (Rx)

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

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

**Das Beste für**: Komplexe Ereignisverarbeitung, zeitbasierte Operationen, Kombination mehrerer Ereignisströme.

**Rx verwenden, wenn**: Sie benötigen komplexe zeitliche Abfragen (Fenstern, Debouncing, Kombinieren von Streams).

**Verwende ephemere Signale, wenn**: Sie wollen eine einfachere Umfrage-basierte Erfassung, automatische Reinigung oder Integration mit Operation Tracking.

### MediatR-Benachrichtigungen

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

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

**Das Beste für**: Entkoppeltes In-Process-Event-Handling mit mehreren Handlern.

**MediatR verwenden, wenn**: Sie möchten, dass mehrere Handler synchron auf dasselbe Ereignis reagieren.

**Verwende ephemere Signale, wenn**: Sie wollen Umgebungserfassung ohne explizites Abonnement, Selbstreinigungsgeschichte oder Integration mit begrenzter Ausführung.

### Polly Circuit Breaker

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

**Das Beste für**: Resilienz um individuelle Anrufe mit automatischer Staatsführung.

**Polly verwenden, wenn**: Sie benötigen per-Call-Resilienz mit automatischen halboffenen/geschlossenen Übergängen.

**Verwende ephemere Signale, wenn**: Sie wollen Umgebungsbewusstsein über viele Operationen, benutzerdefinierte Schaltungslogik oder Integration mit Operation Tracking.

**Kombinieren Sie sie**: Verwenden Sie Polly in Ihrem Arbeitskörper, senden Sie Signale, wenn Schaltungen Reise.

### Vergleichstabelle

Annäherung der Selbstreinigung Ambient Sensing Entkoppelt Benutzerdefinierte Logic-Integration
|----------|:-------------:|:---------------:|:---------:|:------------:|:-----------:|
Offene Telemetrie Externe Werkzeuge
Reaktive Erweiterungen Komplex
MediatR-Handbuch
Polly Circuit Breaker (Pully Circuit Breaker)
| **Vergängliche Signale** Eingebaut

---


## Signale erheben und zurückziehen

Durchführung der Maßnahmen `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; }
}
```

Im Inneren Ihres Arbeitskörpers:

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

Signale sind nur Strings. Verwenden Sie einfache Namen (`"rate-limit"`) oder strukturierte Namen (`"rate-limit:5000ms"`).
Musterfilter verwenden Glob Semantik (`*`, `?`) und Unterstützung Komma-Listen (`"error.*,timeout"`). Matching ist deterministisch und Allokation-Licht über `StringPatternMatcher`.

### Feinkörniges Signal Beispiel: HTTP-Aufrufe

Für eine sehr detaillierte Beobachtbarkeit, können Sie Signale in jeder Phase einer Operation aussenden. Die Bibliothek enthält eine Probe [SignalisierungHttpClient](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Examples/SignalingHttpClient.cs) Das zeigt dieses Muster:

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

Dies sendet Signale in jeder Phase aus:

Signal, wenn
|--------|------|
| `stage.starting` Vor Beginn der Anfrage
| `progress:0` Erstes Fortschrittszeichen
| `stage.request` HTTP-Anfrage gesendet
| `stage.headers` Ansprech-Header erhalten
| `stage.reading` Beginnen, Körper zu lesen
| `progress:XX` Progress-Prozentsatz (0-100) während des Downloads
| `stage.completed` Download beendet

Sie können diese dann mit Muster-Matching abfragen:

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

### Signale zurückziehen

Operationen können auch ihre eigenen Signale entfernen. Dies ist nützlich für temporäre Zustände:

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

### Retraktionsereignisse

Genau wie die Signalemission können Retraktionen Rückrufe auslösen:

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

Das `SignalRetractedEvent` umfasst:

- `Signal` - Der eingefahrene Signalname
- `OperationId` - Die Operation, die sie zurückgezogen hat
- `Key` - Der Schlüssel der Operation (falls vorhanden)
- `Timestamp` - Wenn es zu einer Retraktion kam
- `WasPatternMatch` - Stimmt, wenn über `RetractMatching`
- `Pattern` - Das verwendete Muster (wenn Muster übereinstimmen)

### Real-World Beispiel: 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;
    }
});
```

---


## Signale sensibilisieren

Alle Koordinatoren bieten eine optimierte Signalabfrage:

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

Von [EphemeralOptions.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/EphemeralOptions.cs):

Koordinatoren können automatisch auf Signale reagieren:

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

Wenn ein Signal in `CancelOnSignals` wird erkannt, neue Elemente werden übersprungen (als fehlgeschlagen gezählt).
Wenn ein Signal in `DeferOnSignals` wird erkannt, neue Elemente warten, bis das Signal löscht.

---


## Real-World Beispiel: Adaptive Rate Limiting

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

Jede Instanz dieses Dienstes schaltet sich automatisch ab, wenn die Geschwindigkeitsbegrenzungen getroffen werden. Kein geteilter Zustand. Keine Nachricht. Einfach das ephemere Fenster lesen.

---


## Real-World Beispiel: Cross-Coordinator-Bewusstsein

Mehrere Koordinatoren können sich durch eine gemeinsame `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);
    }
}
```

---


## Real-World Beispiel: Gesundheitsüberwachung

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

Keine Metriken-Bibliothek erforderlich. Abfragen Sie einfach das ephemere Fenster.

---


## Real-World Beispiel: Signalbasierte Schaltung bricht

Von [SignalbasiertCircuitBreaker.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);
```

Der Leistungsschalter hat keinen eigenen Zustand - er liest nur das ephemere Fenster.

---


## Signalbeschränkungen: Unendliche Schleifen verhindern

Von [Signale.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Signals.cs):

Wenn Signale andere Signale verursachen können, riskieren Sie unendliche Schleifen. `SignalConstraints` verhindert dies:

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

### Signalverbreitung

Verfolgen Sie die Kausalität mit `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);
    }
}
```

Die Ausbreitungskette verfolgt den Weg: `order-placed → inventory-reserved → ...`

Wenn `inventory-reserved` versucht zu emittieren `order-placed`, es würde blockiert werden (Zyklus erkannt).

---


## Der SignalSink: Globaler Signalraum

Von [Signale.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/Signals.cs):

Für Signale, die über die Koordinatoren sichtbar sein müssen:

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

Verwendung:

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

---


## Muster passend zu StringMusterMatcher

Von [StringMusterMatcher.cs](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Helpers/Ephemeral/StringPatternMatcher.cs):

Glob-Style-Matching für die Signalfilterung:

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

Halten Sie Signale einfach und konsistent:

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

---


## Bearbeitung von Async-Signalen

Synchrone Signalabnehmer (`OnSignal`) laufen auf dem Thread der Operation – halten Sie sie schnell. Um I/O-gebundene Arbeit zu tun, signalisiert Lüfter in einen async-Pfad mit `SignalDispatcher` (Muster-Matching, deterministische Reihenfolge) oder `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 chor options, keep OnSignal cheap and enqueue
var coordinator = new EphemeralWorkCoordinator<Request>(
    body,
    new EphemeralOptions
    {
        OnSignal = dispatcher.Dispatch
    });
```

Unterstützung von Mustern `*`, `?`, und Kommalisten (`"error.*,timeout"`). Alle passenden Handler laufen in Registrierungsreihenfolge auf einem hintergrundgeschlüsselten Koordinator; emittieren bleibt synchron.

### AssyncSignalProzessor

Zur eigenständigen async-Verarbeitung:

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

### TelemetrieSignalHandler Beispiel

Ein komplettes Beispiel für die Kombination von async Signalverarbeitung und Telemetrieintegration:

> **Quelle:** [TelemetrieSignalHandler.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();
}
```

Überweisen Sie es an Ihren Koordinator:

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

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

Der Handler:

- **Nie blockiert** das Betriebsgewinde - `OnSignal` kehrt sofort zurück
- **Kategorisiert Signale** durch Präfix für verschiedene Telemetrietypen
- **Ergibt Metriken** (gequetscht, verarbeitet, fallengelassen) zur Überwachung der Gesundheit
- **Grenzen Speicher** - Die ältesten Signale fallen, wenn sich die Warteschlange füllt

---


## Warum das funktioniert

Signale sind mächtig, weil sie **ephemer**:

Vermögenswert
|----------|---------|
| **Gebunden** Das Alter kann nicht unbeschränkt wachsen
| **Selbstreinigung** Kein Bereinigungscode erforderlich
| **Entkoppelt** Emitter wissen nichts über Hörer
| **Beobachtbar** Jeder Code kann den aktuellen Zustand spüren
| **Privat** Keine Benutzerdaten - nur Signalnamen
| **Schnell** Detektion von O(1) mit Kurzschluss

Das ephemere Fenster ist schon zum Debuggen da. Signale geben ihm nur semantische Bedeutung.

---


## Schlußfolgerung

Signale verwandeln isolierte Ausführungsatome in eine **Erfassungsnetz**. Jeder Koordinator kann

- **emittieren** Signale über das, was es erlebt hat
- **Sinn** Signale aus der eigenen Geschichte oder einem gemeinsamen Waschbecken
- **Reagieren** automatisch über `CancelOnSignals` und `DeferOnSignals`

Kein Nachrichten-Broker, kein geteilter Zustand, kein Koordinationsprotokoll, nur Operationen mit Metadaten, die natürlich zerfallen.

Die Atome sprechen nicht direkt miteinander - sie hinterlassen nur Spuren in dem ephemeren Fenster, das andere beobachten können. Es ist Stigmatisierung für async Systeme.

**Feuer, Signal, Sinn, Vergessen.**

---


## Verknüpfungen

- [Teil 1: Feuer und nicht *Ganz und gar nicht* Vergessen](/blog/fire-and-dont-quite-forget-ephemeral-execution) - Theorie und Muster
- [Teil 2: Aufbau einer wiederverwendbaren Ephemeral Execution Library](/blog/ephemeral-execution-library) - die vollständige Umsetzung
- [Stigmatisierung (Wikipedia)](https://en.wikipedia.org/wiki/Stigmergy) - indirekte Koordinierung durch Umweltänderung
- [Leistungsschalter-Muster](https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker)
- [Reaktive Erweiterungen](https://github.com/dotnet/reactive)