# Vuur en niet doen. *Nogal* Vergeet - Tijdelijke executie zonder staat

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

De meeste async systemen herinneren zich te veel (logs, wachtrijen, aanhoudende troep die je nooit wilde)... of ze herinneren zich helemaal niets (brand-en-vergeten zwarte gaten die verdwijnen op het moment dat er iets mis gaat).

Dit artikel introduceert iets anders:

> Een patroon waar elke async operatie een kleintje wordt **trackable**, **gecontroleerd**, **opnieuw op te halen**, **zelfreinigende taakeenheid** -en het hele systeem blijft privé, begrensd, snel en deterministisch.

Het is de metgezel van mijn eerdere artikel, **[Leren LRU's - Wanneer overschrijding van de capaciteit maakt uw systeem beter](/blog/learning-lrus-when-capacity-makes-systems-better)**. Die ene onderzocht hoe begrensd geheugen + glijdend verlopen een overlevingsmechanisme wordt.

Deze verkent de andere helft: **begrensde, kortstondige uitvoering** -hoe een kleine rolbuffer van actieve taken een debugger, een event log en een replay engine wordt, zonder gebruikersgegevens te bewaren.

Het voorbeeld komt uit de vertaling van mijn blog widget - een kleine UI voor het vertalen markdown content on-the-fly. Het ziet er triviaal uit. Onder de motorkap, implementeert het patronen die u kunt stelen voor elke async workflow.

Dit is **Deel 1** van een reeks van twee delen:

- **Deel 1 (dit artikel)**: The Theory, the pattern, and a real-world example
- **[Deel 2: Bouwen van een herbruikbare Tijdelijke Executiebibliotheek](/blog/ephemeral-execution-library)**: De volledige implementatie met DI integratie

## NUGET!!!

[Dit is nu in het meest lucide.efemerals Nuget pakket ook meer dan 20 meestal lucid.efemerals patronen en 'atomen'](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)
[![Licentie](https://img.shields.io/badge/license-Unlicense-blue.svg)](../../UNLICENSE)

[TOC]

---


## Het probleem -Async Workflows Verborgen toestand aanmaken

ASP.NET ontwikkelaars hebben de neiging om te kiezen uit drie slechte opties:

### Vuur-en-vergeet

Jij `Task.Run()` Geen verband, geen reconstructie, geen idee wat mislukt is.

### Vuur en wacht.

Je blokkeert draden die je niet moet blokkeren... de doorvoer sterft... de lattentie explodeert... je gebruikers haten je.

### Overal wachtrijen/kanalen

Nu heb je:

- ongebonden wachtrijen
- hardnekkige toestand die je niet wilde houden
- PII per ongeluk ergens opgeslagen
- een gedistribueerd systeem waar je gewoon een eenvoudige operatie wilde

### Gedistribueerde tracering

Nuttig... maar extern. Ook: het kan niets opnieuw afspelen en lekt vaak gegevens die je nooit van plan was op te slaan.

Wat we eigenlijk willen is:

- **kortlevend**
- **particulier**
- **debugable**
- **gecontroleerd**
- **begrensd**
- **indien nodig op te halen**
- **en dan weg**

Een "net lang genoeg" geheugen van wat het systeem doet - en niets meer.

---


## Het patroon -Vuur en *Doe het niet.* Vergeet

Hier is het idee in één zin:

> **Kick off een async operatie onmiddellijk, track het expliciet via een TaskComplementationBron, opruim het deterministisch, en houd een kleine rollende buffer van de laatste paar taken, zodat u kunt inspecteren of ophalen ze - zonder het behoud van een gebruiker inhoud.**

Het zit halverwege tussen:

- event sourcing
- traceren
- werkrijen
- futures/beloftes
- kortstondige sessies

...zonder één van hen te worden.

```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 van een kortstondige taak

De kerngegevensstructuur is a `TranslateTask` -een kleine atomaire executie unit ondersteund door een `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);
```

Het bevat:

- Een unieke ID (correleert over de gehele flow)
- Een tijdstempel (toen het begon)
- Metadata (taal -geen gebruikersinhoud!)
- Een verwijzing naar de werkelijke `Task<TaskCompletion>`
- Duur (berekend bij toegang)
- Foutstatus (afgeleid van taakstatus)
- Eindresultaat (alleen indien succesvol voltooid)

Niets bleef bestaan, niets op schijf geschreven, niets opgeslagen achter een rolraam.

AWS heeft stapfuncties. Azure heeft duurzame functies. Je hebt... 30 regels code die geen cloud factuur nodig hebben.

---


## De taakvoltooiingBronpatroon -Bridging Fire-and-Forget to Fire-and-Track

De magie gebeurt in de `BackgroundTranslateService`. In plaats van vuur-en-vergeet, gebruiken we een [TaakvoltooidBron](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) -een belofte-achtige constructie die ons onmiddellijk terug laat keren terwijl het werk op de achtergrond gebeurt.

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

### Wat is een TaskComplementatieBron?

Als u niet hebt gebruikt `TaskCompletionSource<T>` voor, zie het als een **beloven dat u handmatig controleert**. In tegenstelling tot een reguliere `Task` dat voltooid wanneer het werk klaar is, een TCS voltooid wanneer *u* oproep `SetResult()`, `SetException()`, of `SetCanceled()`.

Dit laat je:

1. Retourneren a `Task` onmiddellijk naar de beller
2. Doe het eigenlijke werk ergens anders (verschillende draad, achtergrond service, enz.)
3. De taak voltooien wanneer *u* Beslis dat het gebeurd is.

Het is de brug tussen "vuur-en-vergeten" en "vuur-en-spoor."

---


## De API-laag - Een vertaling starten

Wanneer een gebruiker op "Vertalen" klikt, roept de frontend de API aan:

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

De API geeft onmiddellijk terug met slechts een taak-ID:

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

Het antwoord is direct. De werkelijke vertaling draait op de achtergrond. De gebruiker kan poll of gewoon kijken naar de UI-update.

---


## De Rolling Operatie Venster - Een Zelfreinigende Buffer

Het belangrijkste inzicht van de [LRU-artikel](/blog/learning-lrus-when-capacity-makes-systems-better) geldt ook hier:

> **Hou net genoeg geschiedenis om te debuggen en redeneren over het systeem... en laat natuurlijk verval al het andere wissen.**

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

Elke nieuwe taak:

- Wordt toegevoegd aan het rolvenster van de gebruiker
- Duwt uit de oudste als we op capaciteit (5 max)
- Verdampt volledig na 6 uur (absolute) of 1 uur inactiviteit (glijden)

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

Geen retentierisico. Geen PII in de cache (alleen taak-ID's, tijdstempels en taalcodes). Geen GDPR hoofdpijn.

---


## Bounded Concurrency - De gouverneur Loop

De background service implementeert een **vaste concurrency** lus met behulp van een kanaallezer en `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");
        }
    }
}
```

Dit is als een **gouverneur op een stoommachine**: meer belasting → tegendruk bouwt → natuurlijke throttling → stabiliteit.

Het patroon geeft je:

- **Geen overbelastingspiraal** -Kan geen onbeperkte taken genereren
- **Zacht real-time gedrag** -gebonden latentie
- **Natuurlijke smoothing van uitbarstingen** - kanaal buffert de spikes
- **Stabiliteit onder belasting** - degradeert sierlijk

---


## Afronden van de taakvoltooiingBron

Wanneer het eigenlijke vertaalwerk klaar is, voltooien we de 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);
    }
}
```

De beller's `Task<TaskCompletion>` -die ze meteen kregen toen ze belden. `Translate()` Ze kunnen...

- `await` het als ze willen blokkeren
- Controleren `IsCompleted` om te peilen
- Controleren `IsFaulted` om te zien of het mislukt is
- Controleren `Result` om de vertaalde inhoud te krijgen

---


## De statusprojectie - Staat van de taakstaat

Bij het weergeven van taken aan de gebruiker, projecteren we de live taakstatus in een weergavemodel:

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

### A Note on Task State

.NET's `Task` heeft een aantal eigenzinnige staat combinaties:

- `IsCompleted` is waar voor **beide** succesvolle voltooiing EN foutieve taken
- `IsCompletedSuccessfully` is alleen waar voor succes
- `IsFaulted` Dat betekent dat het een uitzondering heeft gemaakt.
- `IsCanceled` betekent dat het geannuleerd is.

Dus je moet controleren `IsFaulted` alvorens te controleren `IsCompleted`Of je behandelt mislukkingen als successen.

---


## De UI -HTMX Polling

De peilingen voor updates met behulp van HTMX's `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>
```

Het slimme stukje: `hx-trigger="@trigger"` veranderingen op basis van status:

- Als de taken nog lopen: peiling om de 5 seconden
- Als alle taken zijn uitgevoerd: stop met peilen (`"none"`)

Dit is **zelfregulerende peilingen** -het stopt automatisch als er niets is om naar te kijken.

---


## Het resultaat wordt opgehaald

Wanneer de gebruiker op "View" klikt, halen we de complete vertaling op:

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

De JavaScript bevolkt vervolgens de 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);
        });
}
```

---


## Waarom dit Privacy-Safe is

Omdat:

- **Er is geen gebruikersinhoud opgeslagen in de cache** -alleen taakmetadata (ID, tijdstempel, taal)
- **Inhoud bestaat alleen in het resultaat van de taak** -die in het geheugen is, verbonden aan de taak
- **Het venster is begrensd** -max 5 taken per gebruiker, max 6 uur retentie
- **Alles vergaat van nature.** -sliding verlopen reinigt inactieve gebruikers
- **Niets raakt de schijf aan** -geen logs, geen wachtrijen, geen database
- **U kunt niet per ongeluk PII behouden** - Er is geen plek om naartoe te gaan.

Dit is de architectuur. *Ik wou dat browsers en frontend kaders gebruikt hadden voor sessiebewerkingen*.

---


## Het opkomende gedrag

Wat je krijgt zonder te hard na te denken:

Hoe kan ik je helpen?
|---------|-----|
| **Stabiliteit** Bounded concurrency via kanaal + emafore patroon
| **Negatieve terugkoppelingslus** Meer belasting → tegendruk → begrensd geheugen → consistent gedrag
| **Zichtbaarheid debuggen** De exacte operaties zijn net lang genoeg aanwezig om te inspecteren .
| **Ophalbaarheid** Voltooide resultaten zijn toegankelijk tot ze aflopen .
| **Nul gegevensbewaring** Privacy en eenvoud uitgelijnd voor een keer
| **Zelfoptimaliserende uitvoering** Oude taken vallen weg, relevante blijven over

Net zoals de LRU-cache het gedragsgeheugen scherpde, scherpt het rollende operatievenster de uitvoeringsweergave aan.

---


## Wanneer moet u dit patroon gebruiken?

Gebruik het wanneer:

- Gebruikersgegevens mogen niet aanhouden
- Operaties zijn van korte duur (seconden tot minuten)
- Debuggen is van belang
- Privacyzaken
- De stabiliteit van de lading is van belang
- U wilt direct antwoord + achtergrondverwerking

Gebruik het niet als:

- Je hebt echte duurzaamheid nodig (gebruik een echte wachtrij)
- Operaties duren uren (gebruik Hangfire of dergelijke)
- Je moet precies-eens semantiek (gebruik een gedistribueerde transactie)
- Meerdere servers moeten coördineren (gebruik Redis of een berichtenmakelaar)

---


## Conclusie -Efemeral Execution as a Design Philosophy

Indien de [LRU-artikel](/blog/learning-lrus-when-capacity-makes-systems-better) was ongeveer **leren door te vergeten**, deze gaat over **uitvoeren door verdamping**.

Het systeem herinnert zich precies genoeg om behulpzaam te zijn -en niets meer.

Het is:

- begrensd
- Deterministisch
- particulier
- debugable
- ophalen
- zelfreinigend
- robuust
- en architectuur-eerste

Het beste ding? Je weet al hoe het te implementeren: een `TaskCompletionSource`, a `Channel`, een `IMemoryCache` met een glijdend verloop, en een achtergrond werknemer.

**Vuur... en vergeet het niet.**

---


## Wat is het volgende?

In **[Deel 2: Bouwen van een herbruikbare Tijdelijke Executiebibliotheek](/blog/ephemeral-execution-library)**We veranderen dit patroon in een drop-in helper:

- `EphemeralForEachAsync<T>` - zoals `Parallel.ForEachAsync` maar met tracking
- Getoetste pijpleidingen voor sequentiële uitvoering per entiteit
- `EphemeralWorkCoordinator<T>` - een langlevende waarneembare werkrij
- Vernoemde/getypte coördinatoren met `AddEphemeralWorkCoordinator<TCoordinator>` (zoals `AddHttpClient`)
- Volledige DI-integratie met scoped en singleton lifetimes
- Vergelijking met andere benaderingen (TPL Dataflow, kanalen, achtergronddiensten)

---


## Links

- [Leren LRU's - Wanneer overschrijding van de capaciteit maakt uw systeem beter](/blog/learning-lrus-when-capacity-makes-systems-better) -het metgezel-artikel over begrensd geheugen
- [Deel 2: Bouwen van een herbruikbare Tijdelijke Executiebibliotheek](/blog/ephemeral-execution-library) - de volledige tenuitvoerlegging
- [TaakvoltooienBrondocumentatie](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) -Microsoft's documenten over het TCS patroon
- [Systeem.Threading.Channels](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels) - de producent-consument primitief die wij gebruiken
- [IMemoryCache documentatie](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory) ASP.NET's ingebouwde cache