多数合成系统要么记住太多(logs, chenes, standing junky you never wanted)... 要么它们什么都不记得(火和忘记的黑洞一旦出事就会消失) 。
本条提出一些不同的说法:
一种模式, 每一个同步操作都变成一个微小的 可追踪, 可检查, 可再回收, 自我清洁任务单位 - 整个系统都是私密的 封闭的 快速的 决定性的
这是我前一篇文章的随从 **学习 LRU - 超能力何时使你的系统变得更好**该论文探讨了连接的内存+滑动失效如何成为生存机制的问题。
这个探索了另一半: 受约束,即时处决 - 活动任务的小滚动缓冲器会变成调试器、事件日志和重播引擎, 而不保留任何用户数据 。
这个例子来自我的博客的翻译部件 — 一个用于实时翻译标记内容的小界面。 它看起来微不足道。 在引擎盖下, 它可以执行您可以为任何同步工作流程窃取的模式 。
这是 第一部分 第一部分 由两部分组成的系列:
这现在存在于多数卢布的. 短期的Nuget包件中, 也存在于20多个多数卢布的. 短期的图案和“ 原子” 中。.
ASP.
你,你,你 Task.Run() 没有关联,没有重建,不知道什么失败了。
堵住你不应该堵住的线条。 吞吐量会死, 静脉会爆炸。 您的用户讨厌你 。
现在你有:
有用,但外部。此外,它不能重放任何东西, 并且经常泄漏数据 你从未打算存储。
我们真正想要的是:
"只是足够长的"记忆 系统在做什么 - 没有更多了。
一句话里的想法是这样的:
立即取消一个自动同步操作, 明确通过任务完成源来跟踪它, 明确清除它, 并对最后几项任务保持微小的滚动缓冲, 以便您可以检查或检索它们- 而不保留任何用户内容 。
它坐在中间的中间:
...不成为其中任何一个。
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);
它包括:
Task<TaskCompletion>什么都没有 什么都没有写在磁盘上 没有什么存储在滚动窗口之外
AWS有步骤功能,Azure有持久功能,你有...
魔法发生在 BackgroundTranslateService我们不用火和忘记,而是用 任务完成源 - 一种承诺式的构思 让我们在工作在幕后进行时 立即返回
// 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;
}
如果你不使用 TaskCompletionSource<T> 之前,把它当做 承诺手动控制。 不同于经常 Task 当工作完成时完成, TCS 完成时完成 您 您 的 您 您 调 调 调 调 SetResult(), SetException(),或 SetCanceled().
这让你:
Task 立即通知打电话者这是"火与火"和"火与铁轨"之间的桥梁
当用户点击“ Translate”时, 前端调用 API :
// 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 立即返回, 只需要一个任务编号 :
// 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 更新 。
关键的观点 LRR 条款 这里也适用:
保持足够的历史 来调试和解释这个系统... 让自然衰败抹去一切
// 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; }
}
}
每一项新任务:
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
没有保留风险。 在缓存中没有 PII( 只需任务标识、 时间戳和语言代码 ) 。 没有 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");
}
}
}
这就像一个 驾驶蒸汽引擎的州长更多的负载 背压 建立自然振动 稳定。
该模式给您提供 :
当实际翻译工作完成后,我们完成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);
}
}
来电的 Task<TaskCompletion> -当他们打电话来时,他们立刻就拿到了 Translate() -现在完成了,他们可以:
await 如果他们想阻止IsCompleted 民意调查IsFaulted 看看它是否失败Result 获取翻译内容当向用户显示任务时, 我们将实际任务状态投射到查看模型中 :
// 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 网络 Task 有一些怪异的状态组合:
IsCompleted 是真实的 两者兼 成功完成和错误的任务IsCompletedSuccessfully 唯有成功才是真正的成功IsFaulted 意指它抛出一个例外IsCanceled 表示已取消所以,你必须检查 IsFaulted 检查前检查 IsCompleted否则你会把失败视为成功
使用 HTMX 进行更新的视图民调 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>
聪明的一点: hx-trigger="@trigger" 基于状态的变化 :
"none")这是 自我调节投票 - 它自动停止 当没有看。
当用户点击“ 查看” 时, 我们获取完成的翻译 :
// 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);
});
}
因为:
这就是建筑结构 我希望会话操作使用浏览器和前端框架.
你得到什么 而不思考太辛苦:
福利如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 福利 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 的福利 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何 如何
| --------- | ----- |
|---|---|
| 负反馈回路 更多的负载 背压 捆绑的内存 一致的行为 | |
| 调试可见度 准确的操作在现场 只要足够长的时间来检查 | |
| 可再利用性 完成的结果在到期前可以查阅 | |
| 零数据保留率 隐私和简洁 统一了一次 | |
| 自优化执行 旧任务一去不复返, |
就像路运联盟的缓冲器 放大了行为记忆一样 滚动操作窗口也放大了行刑视图
使用时:
不要在以下时间使用它:
如果 LRR 条款 大约 遗忘后学习,这一个是关于 通过蒸发执行.
系统记忆犹新,
这是:
最好的事情?你已经知道如何执行它: TaskCompletionSource, a , a , a , a Channel, a, a IMemoryCache 与滑动到期, 和背景工人。
火... 别忘了
内 **第2部分:建立一个可重复使用的短执行图书馆**我们会把这个模式变成一个助推员
EphemeralForEachAsync<T> - 喜欢 Parallel.ForEachAsync 具有操作跟踪功能EphemeralWorkCoordinator<T> - 长寿命可观察的工作队列AddEphemeralWorkCoordinator<TCoordinator> (类似) AddHttpClient)© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.