# Tuli ja älä *Aikamoista* Unohtakaa teloitus ilman valtiota

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

Useimmat asynkkijärjestelmät joko muistavat liikaa (lokit, jonot, sinnikkäät roskat, joita et koskaan halunnut)... tai he eivät muista yhtään mitään (tulta ja unohda mustia aukkoja, jotka katoavat, kun jokin menee pieleen).

Tässä artikkelissa esitellään jotain erilaista:

> Kaava, jossa jokaisesta asynkistä operaatiosta tulee pieni **Jäljitettävä**, **Tarkastettu**, **Uudelleen noudettavissa**, **itsepuhdistava tehtäväyksikkö** Koko järjestelmä pysyy yksityisenä, rajattuna, nopeana ja deterministisenä.

Se on aiemman artikkelini kumppani. **[LRU:iden oppiminen - Kun suurempi kapasiteetti tekee järjestelmästäsi paremman](/blog/learning-lrus-when-capacity-makes-systems-better)**Tuo tutki, kuinka rajatusta muistista ja liukuvasta vanhenemisesta tulee selviytymismekanismi.

Tämä tutkii toista puolikasta: **Rajoitettu, ohimenevä teloitus** − Miten pieni liikkuva puskuri aktiivisista tehtävistä muuttuu vianetsintä-, tapahtumaloki- ja uusintamoottoriksi ilman, että käyttäjätietoja säilytetään.

Esimerkki on blogini käännöswidgetistä, joka on pieni käyttöliittymä maaliviivan sisällön kääntämiseen lentoon. Se näyttää vähäpätöiseltä. Huppujen alla se toteuttaa kuvioita, joita voi varastaa mihin tahansa asynkkiseen työnkulkuun.

Tämä on **Osa 1** kaksiosaisesta sarjasta:

- **Osa 1 (tämä artikla)**: Teoria, kuvio ja tosimaailman esimerkki
- **[Osa 2: Uudelleenkäytettävän teloituskirjaston rakentaminen](/blog/ephemeral-execution-library)**: Koko täytäntöönpano DI-integraation avulla

## NUGET!!!

[Tämä on nyt enimmäkseen lucid.efemerals Nuget-paketissa myös yli 20 enimmäkseen lucid.efemerals-mallia ja 'atoms'](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)
[![Lisenssi](https://img.shields.io/badge/license-Unlicense-blue.svg)](../../UNLICENSE)

[TOC]

---


## Ongelma - Async Workflows Luo piilotilaa

ASP.NET-kehittäjät valitsevat yleensä kolmesta huonosta vaihtoehdosta:

### Tuli ja unohdus

Sinä `Task.Run()` Toivottavasti se loppuu, ja kun se räjähtää, ei saa mitään.

### Paloa ja odottelua

Latenssi räjähtää ja käyttäjät vihaavat sinua.

### Jonot/kanavat kaikkialla

Nyt sinulla on:

- rajattomia jonoja
- sinnikäs tila, jota ei ollut tarkoitus pitää
- PII tallentui vahingossa jonnekin
- jaettu järjestelmä, jossa halusit vain yksinkertaisen toiminnan

### Hajautettu jäljitys

Hyödyllistä, mutta ulkoista. Se ei myöskään pysty toistamaan mitään ja vuotaa usein tietoja, joita ei ole koskaan tarkoitettu tallentamaan.

Itse asiassa haluamme, että:

- **lyhytaikainen**
- **yksityinen**
- **debuggattava**
- **Tarkastettu**
- **rajatut**
- **noudettavissa tarvittaessa**
- **ja sitten poissa**

"Vielä tarpeeksi pitkä" muisto siitä, mitä järjestelmä tekee, eikä mitään muuta.

---


## Kaava - tuli ja *Älä aivan* Unohda

Yhdessä lauseessa on ajatus:

> **Aloita async-operaatio välittömästi, seuraa sitä nimenomaan TaskCompletionSourcen kautta, puhdista se deterministisesti ja pidä pieni pyörimispuskuri viimeisissä tehtävissä, jotta voit tarkastaa tai noutaa ne – säilyttämättä mitään käyttäjäsisältöä.**

Se istuu puolivälissä:

- tapahtumahankinnat
- jäljitys
- työjonot
- Futuurit/lupaukset
- Äärimmäiset istunnot

...tulematta keneksikään heistä.

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

---


## Efeerisen tehtävän anatomia

Perusdatarakenne on `TranslateTask` -pieni atomin teloitusyksikkö, jonka tukena on `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);
```

Se sisältää:

- Yksilöllinen tunniste (korjautuu koko virtaukseen)
- Aikaleima (kun se alkoi)
- Metadata (kieli - ei käyttäjäsisältöä!)
- Viittaus todelliseen `Task<TaskCompletion>`
- Kesto (laskettu pääsyllä)
- Virhetila (johtuu tehtävätilasta)
- Lopputulos (vain jos suoritettu onnistuneesti)

Mikään ei kestänyt, ei ollut kirjoitettua levylle, ei ollut tallennettuna pyörivän ikkunan taakse.

AWS:llä on Step Functions. Azurella on kestäviä toimintoja. Sinulla on 30 riviä koodia, jotka eivät vaadi pilvilaskua.

---


## TaskComplectionSource -malli - Tulipalojen ja sammutustöiden unohtaminen

Taikuus tapahtuu `BackgroundTranslateService`Palon ja unohduksen sijaan käytämme [Tehtäväntäyttölähde](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) − Luvatun kaltainen rakennelma, jonka avulla voimme palata välittömästi, kun työt tapahtuvat taustalla.

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

### Mikä on TaskCompletionLähde?

Jos et ole käyttänyt `TaskCompletionSource<T>` ennen, ajattele sitä **lupaa hallita manuaalisesti**Toisin kuin tavallinen `Task` joka valmistuu, kun sen työ päättyy, TCS valmistuu, kun *sinä* soitto `SetResult()`, `SetException()`, tai `SetCanceled()`.

Näin voit:

1. Palauta a `Task` soittajalle heti
2. Tee varsinainen työ jossain muualla (erilainen lanka, taustapalvelu jne.)
3. Suorita tehtävä loppuun, kun *sinä* Päätä, että se on tehty.

Se on "tulen ja unohduksen" ja "tulen ja raidan" välinen silta.

---


## API-taso aloittaa käännöksen

Kun käyttäjä napsauttaa "käännä", etuosa kutsuu API:

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

API palaa välittömästi pelkällä tehtävätunnisteella:

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

Vastaus on instant. Varsinainen käännös kulkee taustalla. Käyttäjä voi tehdä kyselyjä tai vain katsoa UI-päivityksen.

---


## Rolling Operation Window - Itsepuhdistava puskuri

Avainnäkemys: [LRU:n artikkeli](/blog/learning-lrus-when-capacity-makes-systems-better) Koskee myös tätä asiaa:

> **Pidä vain sen verran historiaa, että voit selvittää järjestelmän ja antaa luonnollisen rappeutumisen pyyhkiä pois kaiken muun.**

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

Jokainen uusi tehtävä:

- On lisätty käyttäjän vierintäikkunaan
- Työntää ulos vanhimman, jos olemme valmiudessa (5 max)
- Haihduttaa kokonaan kuuden tunnin (absoluuttinen) tai yhden tunnin (liukumaton) jälkeen

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

Ei retentioriskiä. Ei PII:tä välimuistissa (vain tehtävätunnisteet, aikaleimat ja kielikoodit). Ei GDPR-päänsärkyä.

---


## Rajattu valuutta - Kuvernööri Loop

Taustapalvelu toteuttaa **kiinteä vaihtovelkakirja** silmukka käyttäen kanavanlukijaa ja `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");
        }
    }
}
```

Tämä on kuin **kuvernööri höyrymoottorilla**: lisää kuormaa → vastapainetta rakentaa → luonnollista thratting → vakautta.

Kaava antaa sinulle:

- **Ei ylikuormituskierrettä** -ei voi synnyttää rajattomia tehtäviä
- **Pehmeä reaaliaikainen käytös** -sidottu latenssi
- **Murtumien luonnollinen tasaus** - Kanava puskee piikit
- **Vakaus kuormitettuna** - halventaa sulavasti

---


## Tehtäväntäytäntöönpanolähde

Kun varsinainen käännöstyö päättyy, suoritamme TCS:n:

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

Soittajan `Task<TaskCompletion>` - Jonka he saivat heti, kun soittivat `Translate()` He voivat:

- `await` se, jos he haluavat estää
- Tarkista `IsCompleted` kyselyyn
- Tarkista `IsFaulted` katsoa, epäonnistuiko se
- Tarkista `Result` Käännettyjen sisältöjen saamiseksi

---


## Statusprojektori - Deriving State from Task State

Kun näytämme tehtäviä käyttäjälle, projisoimme live-tehtävätilan näkymämalliksi:

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

### Työvaltiota koskeva huomautus

.NET's `Task` on joitakin kummallisia tilayhdistelmiä:

- `IsCompleted` on totta **molemmat** Onnistunut valmistuminen JA vialliset tehtävät
- `IsCompletedSuccessfully` pitää paikkansa vain menestyksestä
- `IsFaulted` tarkoittaa, että se heitti poikkeuksen
- `IsCanceled` tarkoittaa, että se peruttiin

Joten sinun täytyy tarkistaa `IsFaulted` ennen tarkastusta `IsCompleted`, tai kohtelet epäonnistumisia menestyksinä.

---


## UI-HTMX:n pollaus

Näkemyskyselyt päivityksiin HTMX:n avulla `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>
```

Näppärä kohta: `hx-trigger="@trigger"` tilaan perustuvat muutokset:

- Jos tehtävät ovat yhä kesken: kysely viiden sekunnin välein
- Jos kaikki tehtävät on tehty: äänestysten lopettaminen (`"none"`)

Tämä on **itsesääteleviä kannatuskyselyitä** Se pysähtyy automaattisesti, kun ei ole mitään katsottavaa.

---


## Tulosten hakeminen

Kun käyttäjä napsauttaa "View" -painiketta, haemme valmiin käännöksen:

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

Javascript kansoittaa editorin:

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

---


## Miksi tämä on yksityisyyden suojaa

Koska:

- **Välimuistiin ei tallenneta käyttäjäsisältöä** -vain tehtävän metatiedot (henkilötunnus, aikaleima, kieli)
- **Tehtävän tuloksessa on vain sisältöä** -joka on muistissa, mukana tehtävässä
- **Ikkuna on rajattu** -Maksimi 5 tehtävää käyttäjää kohti, enimmäiskesto 6 tuntia
- **Kaikki lahoaa luonnostaan** - liukuva vanheneminen puhdistaa toimettomia käyttäjiä
- **Mikään ei kosketa levyä** -ei lokitietoja, ei jonoja, ei tietokantaa
- **PII:tä ei voi vahingossa säilyttää** - Ei ole paikkaa minne mennä.

Tämä on arkkitehtuuria *Toivon, että selaimissa ja etukäteiskehyksissä olisi käytetty istuntotoimintoja*.

---


## Tuleva käytös

Mitä saa ajattelematta liikaa:

Hyödy miten?
|---------|-----|
| **Vakaus** Rajattu valuutanvaihto kanavan ja semaforisen kuvion kautta
| **Negatiivisen palautteen silmukka** Lisää kuormitusta → vastapainetta → rajoitettua muistia → johdonmukaista käytöstä
| **Vianetsintänäkyvyys** Tarkat toiminnot ovat läsnä vain sen verran kauan, että ne voidaan tarkastaa.
| **Takaisinperittävyys** Täydennetyt tulokset ovat saatavilla, kunnes ne päättyvät.
| **Nollatietojen säilyttäminen** Yksityisyys ja yksinkertaisuus ovat kerrankin linjassa
| **Itsekeskeinen teloitus** Vanhat tehtävät vähenevät, merkitykselliset jäävät

Aivan kuten LRU:n välimuisti teroitti käyttäytymismuistia, pyörivä leikkausikkuna terävöittää suoritusnäkymää.

---


## Milloin tätä kaavaa käytetään

Käytä sitä, kun:

- Käyttäjätiedot eivät saa säilyä
- Toiminta on lyhytkestoista (sekunneista minuutteihin)
- Vianetsintäasiat
- Yksityisyysasiat
- Kuormanvakavuudella on merkitystä
- Haluat pikavastauksen + taustakäsittelyn

Älä käytä sitä, kun:

- Tarvitset todellista kestävyyttä (käytä oikeaa jonoa)
- Operaatiot kestävät tunteja (käytä Hangfirea tai vastaavaa)
- Tarvitset tasan kerran semantiikkaa (käytä jaettua tapahtumaa)
- Useita palvelimia on koordinoitava (käytä Redisiä tai viestinvälittäjää)

---


## Johtopäätös - Efeerinen teloitus suunnittelufilosofiana

Jos [LRU:n artikkeli](/blog/learning-lrus-when-capacity-makes-systems-better) oli noin **oppiminen unohtamalla**Tässä on kyse siitä, että **Haihduttamalla**.

Järjestelmä muistaa juuri sen verran, että siitä voi olla apua - eikä muuta.

Se on:

- rajatut
- deterministinen
- yksityinen
- debuggattava
- noudettavissa
- itsepuhdistus
- järeä
- Arkkitehtuuri ensin

Parasta on se, että osaat jo toteuttaa sen: `TaskCompletionSource`, a `Channel`, a `IMemoryCache` liukuvan vanhenemisen ja taustatyöntekijän kanssa.

**Ampukaa, älkääkä unohtako.**

---


## Mitä seuraavaksi?

Sisään **[Osa 2: Uudelleenkäytettävän teloituskirjaston rakentaminen](/blog/ephemeral-execution-library)**Muutamme kuvion apuriksi:

- `EphemeralForEachAsync<T>` - kuten `Parallel.ForEachAsync` mutta toiminnan jäljityksellä
- Avatut putkistot per yksikkö peräkkäistä toteutusta varten
- `EphemeralWorkCoordinator<T>` - pitkähkö työjono
- Nimetyt/kirjoitetut koordinaattorit `AddEphemeralWorkCoordinator<TCoordinator>` (kuten `AddHttpClient`)
- Täydellinen DI-integraatio laajakantoisten ja yksitoikkoisten elinimien kanssa
- Vertailu muihin lähestymistapoihin (TPL Dataflow, kanavat, taustapalvelut)

---


## Linkkejä

- [LRU:iden oppiminen - Kun suurempi kapasiteetti tekee järjestelmästäsi paremman](/blog/learning-lrus-when-capacity-makes-systems-better) -kumppanin artikkeli rajatusta muistista
- [Osa 2: Uudelleenkäytettävän teloituskirjaston rakentaminen](/blog/ephemeral-execution-library) -täydellinen täytäntöönpano
- [TehtäväKokoelmaLähdedokumentaatio](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) -Microsoftin dokumentit TCS-kuviosta
- [Järjestelmä.Kuvaus.Kannalit](https://learn.microsoft.com/en-us/dotnet/core/extensions/channels) -tuottaja-kuluttaja-alkukanta, jota käytämme
- [IMemoryCache-dokumentaatio](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/memory) ASP.NETin sisäänrakennettu välimuisti