Back to "火灾和火 不错 忘记无州即时处决"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Architecture ASP.NET Async CQRS Privacy-Safe Design Systems Design

火灾和火 不错 忘记无州即时处决

Friday, 12 December 2025

大多数 Async 系统要么记得太多的“( @logs@,}队列 @,}你从未想要过的)… 或者它们完全记不起什么 “(_fire-}(和)-}忘记了一旦出事就会消失的黑洞 ”\ ).}

这篇文章介绍了一些不同的东西:

每个同步操作都变成一个小 可追踪, 可检查, -可回收, 清理小组 @-_和整个系统保持私人状态 ,BARBAR,_Past,}和确定性=.

也是我前一篇文章的伴奏。 学习 LRUs @-}当超能力使你的系统变得更好时@. 探究被捆绑的内存 + 滑动失效如何成为生存机制 *.

这只探索另一半 : 约束性, 即时处决 @ -} 如何在未保留任何用户数据的情况下将活动任务的小型滚动缓冲变成调试器@ MS K1}事件log,} 和重放引擎\ ,}

这个例子来自我的博客“' @%s transfer complication sublicts ” (@-}一个小界面,用来翻译在 @-_theQZ-}♪Fly上的标记降价内容) 它看起来微不足道的.}(根据Hang*,}它执行模式 你可以为任何 Async工作流程窃取#.}

这是 部分#1 -=YTET -伊甸园字幕组=- 翻译:

NUGET!!!

这现在也出现在大多数卢西德.\ ephearals Nuget 软件包中 超过 @20 @ mostlucid@ . @epheperals 模式和%'#atoms@'}.

Nuget 元数 驾照许可证


源代码文件

完整源代码在 基特Hub 库

问题 @-Async 工作流程创建隐藏状态

ASP.NET 开发商往往会从三个错误选项中选取@:

消防队和消防队1号

你,你 Task.Run() 希望它能完成 ,, 当爆炸时你一无所获... ...

消防队,等等

您应该设置块状线 : @ ' @ t stluke@ MS K1 @ 输给人死亡 @ MPK2 @ 挂载点爆炸@ I.} 您的用户讨厌您@ OMK4

各个角落的通道

现在你有了:

  • 无约束的队列
  • 您没有想要保持的恒定状态@ ' @ t mean to keep
  • PII 意外存放在某处
  • 一个分布式系统,您只要一个简单的操作

分布追踪跟踪

使用@ … @ but external *.} (外出) 还有:} 它可以重放任何您从未打算存储的数据, 并经常泄漏数据

我们真正想要的是:

  • 短的-生命
  • 私立
  • 可调试
  • 可检查
  • 受约束
  • 必要时可回收
  • 然后又离开,

“"”只是时间足够长的“"”记忆系统正在做什么?


模式 @-Fire DonóMSKO 相当 忘记

在这里,' 在一个句子中的想法\ :

立即取消一个 Async 操作@, @ 明确通过任务完成源 <% , > 来跟踪它, 并保存最后几项任务的微小滚动缓冲, 这样您就可以检查或检索这些任务 {-} 而不保留任何用户内容.}

它坐在中间 之间:

  • 外部承包
  • 追踪跟踪
  • 求职队列
  • 未来
  • 预时会议

@…_没有成为其中任何一个#.}

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

时间任务解剖

核心数据结构 TranslateTask *-一个小原子执行单位 后背有一个 Task<TaskCompletion>:

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

里面写着:

  • 整个流程中独有的代号“(”和“correcomlates”, “)”
  • 时间戳@(}当它开始的时候 @)
  • @(語言@-}沒有使用者內容{!)
  • 提及实际 Task<TaskCompletion>
  • 存取时间段@( @compact
  • 错误状态@(来自任务状态 @)
  • 最后结果 #% ( @ 只有当成功完成后 # )\ #

{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}没有东西会一直写到磁盘上...

AWS 具有阶梯函数@.Azure 有持久功能 @. 您有#… @ @ 30线条代码, DONZ'}不需要云单Q.{


任务完成源模式 @ - @ bridging fire@ MS K1}和\ -Fogget to FireQ-_ & @-#Track

魔法发生在 BackgroundTranslateService“. 而不是开火”“- 和“MS K2 forget @, 我们使用一个 任务完成源 @-a promise@-}类似构造让我们在工作发生时立即返回,

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

任务完成源@? @%

如果您已使用@ ' @% t TaskCompletionSource<T> 在, 之前把它当做 承诺手动控制*.* 不同于常规 Task 当工作完成后 ,+GSCS完成时 您 的您 您 调 SetResult(), SetException(),或 SetCanceled().

这让您可以 :

  1. 返回a Task 立即通知打电话者
  2. 执行在其它地方的实际工作 @ ( @ different thread ,} 背景服务@ MS K2 @ etc=.)}
  3. 完成任务时 您 的您 您 @'%s done

这是“" fire” “-”和“MS K3”之间的桥梁。


API 图层 @ -}启动翻译

当用户点击“ "” 时, 前端会调用 API @ MS K2}@ translate",}

// 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 立即返回, 只需执行任务 ID: @

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

回应是即时@.}背景中的实际翻译运行 * @.}用户可以进行民意调查或只看UI最新更新*.}


滚动操作窗口 @ -_ A 自定义@ MS K1\ 清除缓冲

关键的观点 LRU 条款 在此应用@ : @%

保存足够多的历史来调试和解释系统@… @让自然衰变抹去所有其它的#. @

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

每次新任务:

  • 添加到用户 '}s滚动窗口
  • 如果能将最小的 ' 推出最大 MSC2 *
  • 在“6” 小时后完全消失, @(absolute@)或 “1}不活动时间”\ (sliding @MS K5
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

在缓存中没有保留风险@.No PII @(}任务 ID *, timetamps,和语言代码). 没有GDPR头痛=.


州长圈

背景服务处实施 固定货币 使用频道阅读器和 Task.WhenAny:

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

这就像一个 州长在蒸汽机上@:}更多加载 →背压 建筑#→自然节奏 →稳定性.

此模式给予您@ : @%

  • 无超负荷螺旋 -can't 产卵无限的任务
  • 柔软的实际行为@- @时间行为 - 被困的延迟
  • 连续暴发的自然平滑 -通道缓冲尖峰
  • 稳定在装载中 优雅的降级

完成任务完成源

当实际翻译工作完成时 @,我们完成了 TCS:#

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

呼唤者 @'%s Task<TaskCompletion> 当他们打电话来时 他们立刻得到的- Translate() 现在完成 他们可以完成:

  • await 如果他们想阻止
  • 检查支票 IsCompleted 民意调查
  • 检查支票 IsFaulted 看看它是否失败
  • 检查支票 Result 获取翻译内容

来自特派国的状态预测 {-}_建立国

当向用户显示任务时 , 我们将活的任务状态投射到一个查看模型\ MPK1\

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

关于特派国家的说明

.NET @NSK1%s# Task 有一些怪异的状态组合 @:

  • IsCompleted 是真实的 两者兼 成功完成和错误的任务
  • IsCompletedSuccessfully 只有成功才是真正的
  • IsFaulted 意指它抛出一个例外
  • IsCanceled 表示已取消

所以,你必须检查 IsFaulted 检查前 IsCompleted将失败视为成功... .......


UI I-HTMX投票

使用 HTMX @ ' @%s 更新的视图民调 hx-trigger:

@* 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>

聪明的Bit: hx-trigger="@trigger" 基于 state=:}的更改

  • 如果任务仍然在运行@ : @ 民意调查, 每秒 # MS K1 @ 秒
  • 如果完成全部任务, @:# 停止投票@("none")

这是 @-@regroduction 民调投票 @-it 自动停止 当有#'}没有东西可以看 .


获取结果

当用户点击“ " @ view@ MS K1} ” 时, 我们就可以获取完成的翻译@ MPK2

// 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 然后向编辑器写下“ : ” 。

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

为何这是隐私 @-#Safe

因为:

  • 缓存中没有存储用户内容 @ - @ 仅任务元数据@ MS K1 @ ID @ I, @ timetamp, @ 语言 @ MPK4}
  • 仅包含于任务@' @%s 结果中的内容 在记忆中的- 与任务有关
  • 窗口被捆绑 @ - @ max @ MPK1} 每个用户的任务@ MS K2 @ 最大 {6} 保留时间
  • 万物自然腐烂 -_slipping 过期清除非活动用户
  • 没有任何东西接触磁盘 -no logs @,没有队列@,无数据库
  • 您不能意外保留 PII 那里无处可去

这就是建筑结构 我希望会话操作使用浏览器和前端框架.


新兴行为

你得到什么 而不思考太难":

福利 MS K1 如何 *
稳定 @ 通过频道 +semaphore 模式 +
负反馈回路 @ 更多加载@→背压 →捆绑的内存→ 一贯行为 *#
调试可见度 准确的操作在现场足够长的时间 可以检查
可再利用性 完整结果在到期前可以查阅
零数据保留率 隐私和简单对齐过一次
自动- 优化执行 @ 旧任务落空@,相关任务仍为#

就像 LRU 缓存尖锐化了行为记忆@,}滚动操作窗口放大了执行视图 @.*


何时使用此模式

当“:” 时使用它

  • 用户数据不得持续
  • 操作时间短@ - @ 寿命 # MS K1 @ 秒到分钟Q) @
  • 调试事项
  • 隐私事务
  • 负载稳定性事项
  • 您想要即时响应@ + @ 背景处理

Don'' 使用它当:

  • 您需要真正的耐久性@ ( @ 使用真实的队列 @ MS K1 @ {
  • 行动需要数小时 @(_use Hangfire或类似的@)}
  • 您需要准确的“ - @ once 语义学” # MS K1@ 使用分布式交易=)}
  • 多重服务器需要协调 @(_use Redis 或信件中介@)

作为设计哲学的短期处决

如果 LRU 条款 大约 遗忘后学习这首是关于 通过蒸发执行.

系统记忆精确到可以帮助 @-#,

它的'和:

  • 受约束
  • 确定性
  • 私立
  • 可调试
  • 可回收
  • 自 己 -清洗
  • 强体
  • 和建筑结构-_

最好的事情? 你已经知道如何实施它了 TaskCompletionSource,a Channel# , @ an IMemoryCache 滑动过期@,和背景工人 @.

Fieló… 和唐' T quite forgetMS K2


什么是' 下一步 @?

内 建立可重复使用的短时执行图书馆@ , @ we'# 将把此模式转换成一个投机@ MS K2 @ in helper_ :}

  • EphemeralForEachAsync<T> 喜欢吗? Parallel.ForEachAsync 具有操作跟踪功能
  • 用于per-}实体相继执行的关键管道
  • EphemeralWorkCoordinator<T> @ - 长- 活生不老的可观测工作队列
  • 命名为类型协调员 AddEphemeralWorkCoordinator<TCoordinator> 类似 @( AddHttpClient)
  • 与覆盖和单吨寿命周期完全的DI集成
  • 与其它方法的比较 @(TPL Datapropolication_,}通道=,背景服务@)

链接链接链接

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.