# Feuer und nicht *Ganz und gar nicht* Vergessen - Vergängliche Hinrichtung ohne Staat

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

Die meisten async Systeme erinnern sich entweder an zu viel (Logs, Warteschlangen, beharrlicher Schrott, den man nie wollte)... oder sie erinnern sich an gar nichts (Feuer-und-vergessene schwarze Löcher, die verschwinden, sobald etwas schief geht).

Dieser Artikel führt etwas anderes ein:

> Ein Muster, bei dem jede async Operation zu einem winzigen **rückverfolgbar**, **inspizierbar**, **Wiederaufladbar**, **Selbstreinigungs-Task-Einheit** -und das ganze System bleibt privat, begrenzt, schnell und deterministisch.

Es ist der Begleiter zu meinem früheren Artikel, **[LRU-Lernen -Wenn die Kapazität übersteigt macht Ihr System besser](/blog/learning-lrus-when-capacity-makes-systems-better)**. Dieser untersuchte, wie begrenzter Speicher + gleitender Ablauf zu einem Überlebensmechanismus wird.

Diese erkundet die andere Hälfte: **gebundene, ephemere Hinrichtung** -wie ein winziger rollender Puffer aktiver Aufgaben zu einem Debugger, einem Ereignisprotokoll und einer Replay-Engine wird, ohne dass dabei Benutzerdaten gespeichert werden.

Das Beispiel stammt aus dem Übersetzungs-Widget meines Blogs - eine kleine Benutzeroberfläche zum Übersetzen von Markdown-Inhalten on-the-fly. Es sieht trivial aus. Unter der Haube implementiert es Muster, die Sie für jeden async-Workflow stehlen können.

Das ist **Teil 1** einer zweiteiligen Reihe:

- **Teil 1 (dieser Artikel)**: Die Theorie, das Muster und ein reales Beispiel
- **[Teil 2: Aufbau einer wiederverwendbaren Ephemeral Execution Library](/blog/ephemeral-execution-library)**: Die vollständige Umsetzung mit DI-Integration

## NUGET!!!

[Dies ist jetzt in der meistlucid.ephemerals Nuget-Paket auch mehr als 20 meistlucid.ephemerals Muster und 'Atome'](https://www.nuget.org/packages?q=mostlylucid&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)

[TOC]

---


## Das Problem -Async Workflows erstellen versteckten Zustand

ASP.NET-Entwickler neigen dazu, aus drei schlechten Optionen wählen:

### Feuervergessen

Sie `Task.Run()` Ich hoffe, es endet, und wenn es explodiert, bekommt man nichts. Keine Korrelation, keine Rekonstruktion, keine Ahnung, was versagt hat.

### Feuer-und-Warte

Sie blockieren Threads, die Sie nicht blockieren sollten. Durchsatz stirbt. Latenz explodiert. Ihre Benutzer hassen Sie.

### Warteschlangen/Kanäle überall

Jetzt haben Sie:

- ungebundene Warteschlangen
- Beharrlicher Zustand, den du nicht behalten wolltest
- PII versehentlich irgendwo gespeichert
- ein verteiltes System, wo Sie nur eine einfache Bedienung wollten

### Verteilte Rückverfolgung

Nützlich... aber extern. Auch: es kann nichts wieder abspielen und oft undicht Daten, die Sie nie beabsichtigt, zu speichern.

Was wir eigentlich wollen, ist:

- **kurzlebig**
- **privat**
- **debuggingfähig**
- **inspizierbar**
- **gebunden**
- **bei Bedarf wiederholbar**
- **und dann gegangen**

Eine "nur lange genug" Erinnerung an das, was das System tut - und nichts mehr.

---


## Das Muster - Feuer und *Nicht ganz.* Vergessen

Hier ist die Idee in einem Satz:

> **Starten Sie sofort eine async-Operation, verfolgen Sie diese explizit über eine TaskCompletionSource, reinigen Sie sie deterministisch und halten Sie einen winzigen Rollpuffer der letzten Aufgaben, damit Sie sie überprüfen oder abrufen können - ohne jegliche Benutzerinhalte zu behalten.**

Es liegt auf halbem Weg zwischen:

- Event-Sourcing
- Rückverfolgung
- Stellen Warteschlangen
- Termingeschäfte/Versprechen
- ephemere Sitzungen

...ohne sie zu werden.

```mermaid
flowchart TB
    subgraph Client["Client Request"]
        R[Start Translation]
    end

    subgraph API["API Layer"]
        A[Create TaskId] --> B[Create TaskCompletionSource]
        B --> C[Queue to Channel]
        C --> D[Return TaskId Immediately]
    end

    subgraph Cache["Ephemeral Cache (Bounded)"]
        E[Store TranslateTask]
        F[Max 5 per user]
        G[6hr absolute / 1hr sliding expiry]
    end

    subgraph Worker["Background Worker"]
        H[Read from Channel]
        H --> I[Execute Translation]
        I --> J[Complete TCS]
    end

    R --> A
    B --> E
    E --> F --> G
    C --> H

    style Cache fill:none,stroke:#10b981,stroke-width:2px
    style Worker fill:none,stroke:#6366f1,stroke-width:2px
```

---


## Anatomie einer ephemeren Aufgabe

Die Kerndatenstruktur ist ein `TranslateTask` - eine winzige Atom-Exekutionseinheit, die von einem `Task<TaskCompletion>`:

```csharp
// From TranslateTask.cs
public class TranslateTask(
    string taskId,
    DateTime startTime,
    string language,
    Task<TaskCompletion>? task)
    : TranslateResultTask(taskId, startTime, language)
{
    public Task<TaskCompletion>? Task { get; init; } = task;
}

public record TaskCompletion(
    string? TranslatedMarkdown,
    string OriginalMarkdown,
    string Language,
    bool Complete,
    DateTime? EndTime);
```

Sie enthält:

- Eine eindeutige ID (korreliert über den gesamten Fluss)
- Ein Zeitstempel (als es anfing)
- Metadaten (Sprache -nicht Benutzerinhalt!)
- Ein Verweis auf die tatsächliche `Task<TaskCompletion>`
- Dauer (berechnet auf den Zugang)
- Fehlerzustand (abgeleitet vom Aufgabenzustand)
- Endergebnis (nur bei erfolgreichem Abschluss)

Nichts blieb bestehen, nichts auf Diskette geschrieben, nichts gespeichert über ein rollendes Fenster hinaus.

AWS hat Step-Funktionen. Azure hat dauerhafte Funktionen. Sie haben... 30 Zeilen Code, die keine Cloud-Bill erfordern.

---


## Das TaskCompletionSource-Muster -Bridging Fire-and-Forget to Fire-and-Track

Die Magie geschieht in der `BackgroundTranslateService`. Statt Feuer-und-vergessen, verwenden wir eine [TaskCompletionSource](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) - ein Versprechen-ähnliches Konstrukt, das uns sofort zurückkehren lässt, während die Arbeit im Hintergrund geschieht.

```csharp
// From BackgroundTranslateService.cs
private readonly Channel<(PageTranslationModel, TaskCompletionSource<TaskCompletion>)>
    _translations = Channel.CreateUnbounded<(PageTranslationModel, TaskCompletionSource<TaskCompletion>)>();

private async Task<Task<TaskCompletion>> Translate(PageTranslationModel message)
{
    // Create a TaskCompletionSource that will eventually hold the result
    var tcs = new TaskCompletionSource<TaskCompletion>();

    // Send the translation request along with the TCS to be processed
    await _translations.Writer.WriteAsync((message, tcs));

    // Return the Task immediately -caller can await it or check status later
    return tcs.Task;
}
```

### Was ist eine TaskCompletionSource?

Wenn Sie nicht verwendet haben `TaskCompletionSource<T>` vor, betrachten Sie es als ein **Versprich, dass du manuell kontrollierst**. Im Gegensatz zu einem regulären `Task` das, wenn seine Arbeit beendet ist, ein TCS vollendet, wenn *Sie* Anruf `SetResult()`, `SetException()`, oder `SetCanceled()`.

So können Sie:

1. Zurückgeben a `Task` sofort zum Anrufer
2. Machen Sie die eigentliche Arbeit irgendwo anders (verschiedene Threads, Hintergrund-Service, etc.)
3. Vervollständigen Sie die Aufgabe, wenn *Sie* Entscheiden Sie, dass es erledigt ist

Es ist die Brücke zwischen "Feuer-vergessen" und "Feuer-vergessen".

---


## Die API-Ebene -Beginn einer Übersetzung

Wenn ein Benutzer auf "Übersetzen" klickt, ruft das Frontend die API auf:

```javascript
// From translations.js
fetch('/api/translate/start-translation', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        Language: shortCode,
        OriginalMarkdown: markdown
    })
})
.then(response => response.json())
.then(taskId => {
    // Got a taskId immediately -translation is running in background
    console.log("Task ID:", taskId);

    // Poll for updates via HTMX
    htmx.ajax('get', "/editor/get-translations", {
        target: '#translations',
        swap: 'innerHTML'
    });
});
```

Die API kehrt sofort mit nur einer Task-ID zurück:

```csharp
// From TranslateAPI.cs
[HttpPost("start-translation")]
public async Task<Results<Ok<string>, BadRequest<string>>> StartTranslation(
    [FromBody] MarkdownTranslationModel model)
{
    if (!backgroundTranslateService.TranslationServiceUp)
        return TypedResults.BadRequest("Translation service is down");

    // Create a unique identifier for this translation task
    var taskId = Guid.NewGuid().ToString("N");
    var userId = Request.GetUserId(Response);

    // Trigger translation -returns Task<TaskCompletion> immediately
    var translationTask = await backgroundTranslateService.Translate(model);

    // Wrap it in our trackable TranslateTask
    var translateTask = new TranslateTask(taskId, DateTime.Now, model.Language, translationTask);

    // Store in the ephemeral cache (bounded, self-cleaning)
    translateCacheService.AddTask(userId, translateTask);

    // Return the task ID to the client -they can poll for status
    return TypedResults.Ok(taskId);
}
```

Die Antwort ist sofort. Die eigentliche Übersetzung läuft im Hintergrund. Der Benutzer kann die UI-Aktualisierung abfragen oder einfach nur ansehen.

---


## Das Rolling Operation Window -Ein selbstreinigender Puffer

Die wichtigsten Erkenntnisse aus der [LRU-Artikel](/blog/learning-lrus-when-capacity-makes-systems-better) gilt auch hier:

> **Behalten Sie gerade genug Geschichte, um zu debuggen und Vernunft über das System... und lassen Sie natürlichen Verfall löschen alles andere.**

```csharp
// From TranslateCacheService.cs
public class TranslateCacheService(IMemoryCache memoryCache)
{
    public void AddTask(string userId, TranslateTask task)
    {
        if (memoryCache.TryGetValue(userId, out CachedTasks? tasks))
        {
            var currentTasks = tasks?.Tasks ?? new List<TranslateTask>();
            currentTasks = currentTasks.OrderByDescending(x => x.StartTime).ToList();

            // Keep only the 5 most recent tasks -bounded window
            if (currentTasks.Count >= 5)
            {
                var lastTask = currentTasks.Last();
                currentTasks.Remove(lastTask);
            }

            currentTasks.Add(task);
            currentTasks = currentTasks.OrderByDescending(x => x.StartTime).ToList();
            tasks!.Tasks = currentTasks;

            memoryCache.Set(userId, tasks, new MemoryCacheEntryOptions
            {
                AbsoluteExpiration = tasks.AbsoluteExpiration,
                SlidingExpiration = TimeSpan.FromHours(1)
            });
        }
        else
        {
            // First task for this user
            var cachedTasks = new CachedTasks
            {
                Tasks = new List<TranslateTask> { task },
                AbsoluteExpiration = DateTime.Now.AddHours(6)
            };
            memoryCache.Set(userId, cachedTasks, new MemoryCacheEntryOptions
            {
                AbsoluteExpiration = cachedTasks.AbsoluteExpiration,
                SlidingExpiration = TimeSpan.FromHours(1)
            });
        }
    }

    public List<TranslateTask> GetTasks(string userId)
    {
        if (memoryCache.TryGetValue(userId, out CachedTasks? tasks))
            return tasks?.Tasks ?? new List<TranslateTask>();
        return new List<TranslateTask>();
    }

    private class CachedTasks
    {
        public List<TranslateTask> Tasks { get; set; } = new();
        public DateTime AbsoluteExpiration { get; set; }
    }
}
```

Jede neue Aufgabe:

- Wird dem Rollfenster des Benutzers hinzugefügt
- Schiebt den ältesten heraus, wenn wir in der Kapazität sind (5 max)
- Verdunstung vollständig nach 6 Stunden (absolut) oder 1 Stunde Inaktivität (Schieben)

```mermaid
flowchart LR
    subgraph Window["Per-User Task Window (Max 5)"]
        T1[Oldest Task] --- T2[Older] --- T3[Recent] --- T4[Newer] --- T5[Newest]
    end

    New[New Task] -->|Enqueue| Window
    T1 -->|Evicted| Gone[(Expired)]

    style Window fill:none,stroke:#10b981,stroke-width:2px
    style Gone fill:none,stroke:#ef4444,stroke-width:2px
```

Kein Retentionsrisiko. Kein PII im Cache (nur Task-IDs, Zeitstempel und Sprachcodes).

---


## Gebundene Übereinstimmung - Die Gouverneursschleife

Der Hintergrunddienst implementiert eine **feste Übereinstimmung** Schleife mit einem Kanalleser und `Task.WhenAny`:

```csharp
// From BackgroundTranslateService.cs
private async Task TranslateFilesAsync(CancellationToken cancellationToken)
{
    var processingTasks = new List<Task>();

    while (!cancellationToken.IsCancellationRequested)
    {
        // Fill up to IPCount concurrent tasks (e.g., 4 parallel translations)
        while (processingTasks.Count < markdownTranslatorService.IPCount &&
               !cancellationToken.IsCancellationRequested)
        {
            var item = await _translations.Reader.ReadAsync(cancellationToken);
            var translateModel = item.Item1;
            var tcs = item.Item2;

            // Start the task and add it to the list
            var task = TranslateTask(cancellationToken, translateModel, item, tcs);
            processingTasks.Add(task);
        }

        // Wait for ANY of the tasks to complete
        var completedTask = await Task.WhenAny(processingTasks);
        processingTasks.Remove(completedTask);

        // Handle exceptions if needed
        try
        {
            await completedTask;
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error translating markdown");
        }
    }
}
```

Das ist wie ein **Gouverneur auf einer Dampfmaschine**: mehr Belastung → Rückendruck baut → natürliche Drosselung → Stabilität.

Das Muster gibt Ihnen:

- **Keine Überlastspirale** - kann nicht laichen unbegrenzte Aufgaben
- **Weiches Echtzeit-Verhalten** -gebundene Latenz
- **Natürliche Glättung von Bursts** -Kanal-Puffer die Spikes
- **Stabilität unter Last** - degradiert anmutig

---


## Vervollständigung der TaskCompletionSource

Wenn die eigentliche Übersetzung beendet ist, vervollständigen wir das TCS:

```csharp
// From BackgroundTranslateService.cs
private async Task TranslateTask(
    CancellationToken cancellationToken,
    PageTranslationModel translateModel,
    (PageTranslationModel, TaskCompletionSource<TaskCompletion>) item,
    TaskCompletionSource<TaskCompletion> tcs)
{
    try
    {
        await retryPolicy.ExecuteAsync(async () =>
        {
            // Do the actual translation work
            var translatedMarkdown = await markdownTranslatorService.TranslateMarkdown(
                translateModel.OriginalMarkdown,
                translateModel.Language,
                cancellationToken);

            // SUCCESS: Complete the TCS with the result
            tcs.SetResult(new TaskCompletion(
                translatedMarkdown,
                translateModel.OriginalMarkdown,
                translateModel.Language,
                true,
                DateTime.Now));
        });
    }
    catch (TranslateException e)
    {
        // FAILURE: Complete the TCS with an exception
        tcs.SetException(new Exception($"Translation failed after 3 retries: {e.Message}"));
    }
    catch (Exception e)
    {
        // UNEXPECTED: Complete the TCS with the exception
        tcs.SetException(e);
    }
}
```

Der Anrufer ist `Task<TaskCompletion>` - die sie sofort bekamen, als sie anriefen `Translate()` - Jetzt fertig. Sie können:

- `await` es, wenn sie blockieren wollen
- Kontrolle `IsCompleted` zur Umfrage
- Kontrolle `IsFaulted` um zu sehen, ob es fehlgeschlagen ist
- Kontrolle `Result` um den übersetzten Inhalt zu erhalten

---


## Die Statusprojektion - Ableiten des Staates aus dem Task-State

Bei der Anzeige von Aufgaben an den Benutzer projizieren wir den Live-Task-Status in ein View-Modell:

```csharp
// From TranslateTask.cs
public TranslateResultTask(TranslateTask task, bool includeMarkdown = false)
{
    TaskId = task.TaskId;
    StartTime = task.StartTime;
    Language = task.Language;

    // Check for faulted state first -a faulted task is also "completed" in .NET terms
    if (task.Task?.IsFaulted == true)
    {
        Failed = true;
        Completed = false;
        TotalMilliseconds = (int)(DateTime.Now - task.StartTime).TotalMilliseconds;
    }
    else if (task.Task?.IsCompletedSuccessfully == true)
    {
        Completed = true;
        Failed = false;
        var endTime = task.Task.Result.EndTime;
        TotalMilliseconds = (int)((endTime - task.StartTime)!).Value.TotalMilliseconds;
        EndTime = endTime;
    }
    else
    {
        // Still in progress
        Completed = false;
        Failed = false;
        TotalMilliseconds = (int)(DateTime.Now - task.StartTime).TotalMilliseconds;
    }

    if (Completed && includeMarkdown)
    {
        var result = task.Task?.Result;
        if (result == null) return;
        OriginalMarkdown = result.OriginalMarkdown;
        TranslatedMarkdown = result.TranslatedMarkdown;
    }
}
```

### Eine Anmerkung zum Task-State

.NET's `Task` hat einige skurrile Zustandskombinationen:

- `IsCompleted` ist wahr für **beides** Erfolgreicher Abschluss UND fehlerhafte Aufgaben
- `IsCompletedSuccessfully` ist nur für den Erfolg wahr
- `IsFaulted` bedeutet, es warf eine Ausnahme
- `IsCanceled` bedeutet, dass es annulliert wurde

Also müssen Sie überprüfen, `IsFaulted` vor der Prüfung `IsCompleted`Oder Sie behandeln Misserfolge als Erfolge.

---


## Das UI -HTMX Polling

Die Ansicht Umfragen für Updates mit HTMX `hx-trigger`:

```html
@* From _GetTranslations.cshtml *@
@{
    var allCompleted = Model.All(x => x.Completed);
    var trigger = allCompleted ? "none" : "every 5s";
}

<div class="translationpoller"
     hx-get="/editor/get-translations"
     hx-swap="outerHTML"
     hx-trigger="@trigger">
    <table class="table">
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @if (item.Completed)
                    {
                        <a href="#" x-on:click.prevent="viewTranslation('@item.TaskId')">View</a>
                    }
                    else if (item.Failed)
                    {
                        <text>Failed</text>
                    }
                    else
                    {
                        <text>Processing</text>
                    }
                </td>
                <td>
                    @if (item.Completed)
                    {
                        <i class='bx bx-check text-green'></i>
                    }
                    else if (item.Failed)
                    {
                        <i class='bx bx-x text-red'></i>
                    }
                    else
                    {
                        <img src="~/img/3-dots-bounce.svg" />
                    }
                </td>
                <td>@item.Language</td>
                <td>@TimeSpan.FromMilliseconds(item.TotalMilliseconds).Humanize()</td>
            </tr>
        }
    </table>
</div>
```

Das clevere Stück: `hx-trigger="@trigger"` Änderungen auf der Grundlage des Zustands:

- Wenn die Aufgaben noch laufen: Umfrage alle 5 Sekunden
- Wenn alle Aufgaben erledigt sind: Beenden Sie die Umfrage (`"none"`)

Das ist **Selbstregulierende Umfragen** - es hört automatisch auf, wenn es nichts zu sehen gibt.

---


## Das Ergebnis abrufen

Wenn der Benutzer auf "Ansicht" klickt, holen wir die fertige Übersetzung:

```csharp
// From TranslateAPI.cs
[HttpGet("get-translation/{taskId}")]
public async Task<Results<JsonHttpResult<TranslateResultTask>, BadRequest<string>>> GetTranslation(
    string taskId)
{
    var userId = Request.GetUserId(Response);
    var tasks = translateCacheService.GetTasks(userId);

    var translationTask = tasks.FirstOrDefault(t => t.TaskId == taskId);
    if (translationTask == null)
        return TypedResults.BadRequest("Task not found");

    // Include the markdown content in the response
    var result = new TranslateResultTask(translationTask, includeMarkdown: true);
    return TypedResults.Json(result);
}
```

Das JavaScript bevölkert dann den Editor:

```javascript
// From translations.js
export function viewTranslation(taskId) {
    fetch(`/api/translate/get-translation/${taskId}`)
        .then(response => response.json())
        .then(data => {
            // Show the translated content area
            document.getElementById("translatedcontent").classList.remove("hidden");

            // Populate the editors
            var originalMde = window.mostlylucid.simplemde.getinstance('translatedcontentarea');
            originalMde.value(data.originalMarkdown);

            var mde = window.mostlylucid.simplemde.getinstance('markdowneditor');
            mde.value(data.translatedMarkdown);
        });
}
```

---


## Warum dies Datenschutz-sicher ist

Weil:

- **Im Cache werden keine Benutzerinhalte gespeichert** -nur Aufgaben-Metadaten (ID, Zeitstempel, Sprache)
- **Inhalt existiert nur im Ergebnis der Aufgabe** -die sich im Gedächtnis befindet, an die Aufgabe angehängt
- **Das Fenster ist begrenzt** -max 5 Aufgaben pro Benutzer, max 6 Stunden Retention
- **Alles verfällt natürlich** -Sliding Ablauf reinigt inaktive Benutzer
- **Nichts berührt Scheibe** -Keine Protokolle, keine Warteschlangen, keine Datenbank
- **Sie können PII nicht versehentlich behalten.** - Es gibt keinen Ausweg.

Das ist die Architektur *Ich wünschte, Browser und Frontend-Frameworks hätten für Session-Operationen verwendet*.

---


## Das neu entstehende Verhalten

Was man bekommt, ohne zu hart zu denken:

Nutzen Wie?
|---------|-----|
| **Stabilität** Begrenzte Übereinstimmung über Kanal + Semaphormuster
| **Negative Rückkopplungsschleife** Weitere Belastung → Rückendruck → gebundener Speicher → konsistentes Verhalten
| **Debug-Sichtbarkeit** Die genauen Operationen sind gerade lange genug vorhanden, um zu inspizieren
| **Abrufbarkeit** Abgeschlossene Ergebnisse sind bis zum Ablauf zugänglich
| **Null Datenspeicherung** Ausschließlich Privatsphäre und Einfachheit für einmal ausgerichtet
| **Selbstoptimierende Ausführung** Alte Aufgaben fallen weg, relevante bleiben

Genau wie der LRU-Cache den Verhaltensspeicher geschärft hat, schärft das rollende Operationsfenster die Ausführungsansicht.

---


## Wann dieses Muster verwendet werden soll

Verwenden Sie es, wenn:

- Benutzerdaten dürfen nicht bestehen bleiben
- Operationen sind kurzlebig (Sekunden bis Minuten)
- Debugging-Angelegenheiten
- Fragen des Datenschutzes
- Laststabilität ist wichtig
- Sie wollen sofortige Antwort + Hintergrundverarbeitung

Verwenden Sie es nicht, wenn:

- Sie brauchen wahre Haltbarkeit (verwenden Sie eine echte Warteschlange)
- Der Betrieb dauert Stunden (Verwendung von Hangfire oder ähnlichem)
- Sie brauchen genau einmal Semantik (verwenden Sie eine verteilte Transaktion)
- Mehrere Server müssen koordinieren (verwenden Sie Redis oder einen Nachrichten-Broker)

---


## Fazit - Ephemere Ausführung als Design-Philosophie

Wenn die [LRU-Artikel](/blog/learning-lrus-when-capacity-makes-systems-better) war ungefähr **Lernen durch Vergessen**, hier geht es um **Ausführung durch Verdunstung**.

Das System erinnert sich genau genug, um hilfreich zu sein - und nichts mehr.

Es ist:

- gebunden
- Deterministisch
- privat
- debuggingfähig
- wiederholbar
- Selbstreinigung
- robust
- und Architektur-erste

Das Beste? Sie wissen bereits, wie man es umsetzt: ein `TaskCompletionSource`, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , `Channel`, eine `IMemoryCache` mit gleitendem Ablauf und einem Hintergrundarbeiter.

**Feuer... und vergessen Sie es nicht.**

---


## Was kommt als Nächstes?

Im **[Teil 2: Aufbau einer wiederverwendbaren Ephemeral Execution Library](/blog/ephemeral-execution-library)**Wir werden dieses Muster in einen Drop-In-Helfer verwandeln:

- `EphemeralForEachAsync<T>` - wie `Parallel.ForEachAsync` aber mit Operationsverfolgung
- Geschlüsselte Pipelines für per-entity sequential execution
- `EphemeralWorkCoordinator<T>` - eine langlebige, beobachtbare Warteschlange
- benannte/typisierte Koordinatoren mit `AddEphemeralWorkCoordinator<TCoordinator>` (wie `AddHttpClient`)
- Volle DI-Integration mit scoped und Singleton-Lebensdauern
- Vergleich mit anderen Ansätzen (TPL Dataflow, Channels, Background Services)

---


## Verknüpfungen

- [LRU-Lernen -Wenn die Kapazität übersteigt macht Ihr System besser](/blog/learning-lrus-when-capacity-makes-systems-better) -der Begleitartikel über das gebundene Gedächtnis
- [Teil 2: Aufbau einer wiederverwendbaren Ephemeral Execution Library](/blog/ephemeral-execution-library) -die vollständige Umsetzung
- [TaskCompletionSource-Dokumentation](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) -Microsoft's docs über das TCS-Muster
- [System.Threading.Channels](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels) - die Erzeuger-Verbraucher primitive wir verwenden
- [Dokumentation von IMemoryCache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory) -ASP.NETs eingebauter Cache