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
Friday, 12 December 2025
一个微小的原始体 将同时进行的工作 变成一个协调的适应系统
"时空信号模式"
内 第一部分 第一部分 我们建造了短时执行 - 捆绑, 私人, 自我清洁的同步工作流程。 In 第二部分 第二部分 我们把它变成一个可再使用的图书馆,有协调员、有钥匙的管道和DI集成。
本条增加了一个改变一切的小特点: 信号信号信号.
这现在也出现在 大多为粗略的. 短暂的 Niget 软件包中 20多个大多为lucid.ephears 模式和“原子”.
完整源代码在 基特Hub 库
信号基础设施存在于:
| ------ | --------- |
|---|---|
| 短期行动cs * 运行中的信号排放和撤回 | |
时间选择cs CancelOnSignals, DeferOnSignals, OnSignal, OnSignalRetracted) |
|
| 书签父母 用于信号过滤的球式模式匹配 | |
信号发送器cs 使用模式匹配的 Async 信号路由( 支持) *, ?逗号列表, 确定性顺序) |
|
| 实例/信号HttpClient.cs HTTP电话的精细颗粒信号排放样本 | |
| 实例/调整性翻译服务cs 适应率受基于信号的推迟限制 | |
| 实例/基于信号的环环 道 环 环 环 环 环 箱.cs 巡回断路器读取时空信号窗口 | |
| 实例/遥测信号手法cs 使用遥测集成的 Async 信号处理 |
我们的时尚协调员善于处理工作,但他们是孤立的。每个协调员都知道自己的行动,但不知道系统其他地方正在发生什么。
// Translation coordinator has no idea that...
await translationCoordinator.EnqueueAsync(request);
// ...the API just hit a rate limit
// ...another service is experiencing backpressure
// ...a downstream dependency is slow
我们可以把明显的依附关系连接起来,但是这会造成联结。我们想要的是, 环境意识 - 协调员能够感知其环境而不直接相连。
让处决原子留下痕迹的信号 在其短暂的窗口。这些痕迹表现得像短命的事实:
然后协调员可以根据窗口中可见的信号改变行为。这是环境意识,没有依赖性。
发自 信号cs:
public readonly record struct SignalEvent(
string Signal,
long OperationId,
string? Key,
DateTimeOffset Timestamp,
SignalPropagation? Propagation = null)
{
public int Depth => Propagation?.Depth ?? 0;
public bool WouldCycle(string signal) => Propagation?.Contains(signal) == true;
public bool Is(string name) => Signal == name;
public bool StartsWith(string prefix) => Signal.StartsWith(prefix, StringComparison.Ordinal);
}
操作可以在执行过程中发出信号。 这些信号存在于运行的时空窗口中。 当操作结束时, 信号随之而去 。
就是这样,没有信息经纪人,没有单独的基础设施,只有操作的附加条件
由于信号存在于时空窗口内,它们继承了它的保证:界限大小、自动老化和零生命周期间接费用。
信号本身并不导致处决。 它只记录在时尚窗口中的事实 没有什么运行是因为信号被发出。
如果协调员给OnSignal处理器下定义,当发出信号时它同步运行,但仅仅因为协调员选择附加信号。 施舍者不知道或不关心。 取消所有处理者,核心行为不变。
信号只附在发射它的原子/操作上。 从来没有信号变异 或注解另一个原子。 没有共享的可执行公交车。
发送信号是原子历史的附加事实。 信号从未更新或覆盖过。 减量只能消除排放者自己的信号。
信号只存在于协调员的时空窗口内。 它们随着窗口的耗尽而自动失效。 没有什么是持久的,除非你明确坚持下去。
当协调员“检查关键 K 的信号”时,它扫描:
窗口中的原子
以及当地对这些原子的信号 观察者从未改变原子的信号状态。
如果您想要信号驱动同步工作流程, 您必须使用 :
信号发送器
Async 信号处理器
或其他适配器。
这些是建立在信号之上的可选层, 而不是其语义的一部分。
任何原子都不能改变另一个原子的快照、状态、信号或元数据。 通过以下方式进行协调:
信号信号信号
遥感
窗口窗口窗口窗口
政策、政策、政策、政策和政策
不是通过写作。
信号汇或群聚表面可能显示对信号的综合观察—— 但它总是只读,从不权威,从不写字。
如果所有处理器(信号、调度器、处理器)都分离, 该系统仍然完全正确和可预测。 信号仍然有意义,因为它们是事实,不是触发物。
事件事件事件事件 就像是一个电话:
"我现在就给你打电话 接电话反应"
信号 象雪中的脚印一样。
"我留下了脚印,如果你想知道我去哪里..."
这就是为什么信号从不断断,从不阻断, 从不与控制流互动,除非 选择 来调查他们。
区别很重要,因为信号消除了:
问题 事件 信号 避免它 |---------|:--------------:|:----------------:|
时间依赖性 即时反应 需要 环境, 民意调查,一旦准备好
特写事件 信号 |---------|--------|---------|
即时时间 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 即时 保证/试图 没有交付,只是存在 回应 要求 任选 任选 任选
事件处理方法(经典):
public event Action RateLimited;
try
{
await CallApiAsync();
}
catch (RateLimitException)
{
RateLimited?.Invoke(); // Makes someone else act right now
}
问题:
信号接近( 短暂) :
try
{
await CallApiAsync(ct);
}
catch (RateLimitException ex)
{
op.Signal($"rate-limit:{ex.RetryAfterMs}ms");
throw;
}
这里什么也没有发生 后来 完全不一样的地方
if (translator.HasSignal("rate-limit"))
{
await Task.Delay(1000); // Act when *we* choose
}
注:
事件传输控制 信号传输环境
这是整个心理模式 在一句话。
取决于您的背景 :
信号是共享的、紧凑的内存表面留下的痕迹。 任何人都可以查看它们。 没有人有反应的义务。 这是根本的。 悬浮 协调模式 -- -- 同一种蚂蚁使用,同一种在早期AI中使用的黑板系统,同一种现代CRDT八卦网络的提示。
using var activity = source.StartActivity("ProcessOrder");
activity?.SetTag("order.id", orderId);
activity?.SetTag("rate.limited", true);
最佳:在服务、长期遥测存储、相关身份等各处分布追踪。
使用遥测时使用遥测方法: 您需要跟踪多个服务的请求, 存储用于分析的量度, 或与监测工具整合 。
使用时间信号:您需要过程内的环境意识, 反应协调, 或者不需要遥测基础设施。
var rateLimits = Observable.FromEventPattern<RateLimitEventArgs>(
h => api.RateLimitHit += h,
h => api.RateLimitHit -= h);
rateLimits
.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(e => HandleRateLimit(e));
最佳:复杂事件处理、基于时间的业务,结合多个事件流。
使用 Rx 当: 您需要复杂的时间查询( 窗口、 拆卸、 组合流) 。
使用时间信号:您需要更简单的基于投票的感应、自动清理或与行动跟踪整合。
public class RateLimitNotification : INotification
{
public int RetryAfterMs { get; init; }
}
await _mediator.Publish(new RateLimitNotification { RetryAfterMs = 5000 });
最佳:与多个处理器分离处理处理处理过程中的事件。
使用中位tR:您想要多个处理器对同一事件同步反应 。
使用时间信号:您想要没有明确订阅、自我清理历史或与捆绑处决结合的环境遥感。
var circuitBreaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
最佳:通过自动国家管理,应对个人电话的复原力。
使用 Polly 时:您需要每次呼叫的复原力,自动进行半开放/封闭的过渡。
使用时间信号:您想要通过许多操作, 定制电路逻辑, 或与操作跟踪整合, 来提高环境意识 。
结合他们:在工作体内使用Polly,在电路出行时发出信号。
| 短讯讯号
业务实施 ISignalEmitter:
public interface ISignalEmitter
{
// Emit signals
void Emit(string signal);
bool EmitCaused(string signal, SignalPropagation? cause);
// Retract (remove) signals
bool Retract(string signal);
int RetractMatching(string pattern);
bool HasSignal(string signal);
long OperationId { get; }
string? Key { get; }
}
在你工作的身体里:
await coordinator.ProcessAsync(async (item, op, ct) =>
{
try
{
var result = await CallExternalApiAsync(item, ct);
if (result.WasCached)
op.Signal("cache-hit");
if (result.Duration > TimeSpan.FromSeconds(2))
op.Signal("slow-response");
}
catch (RateLimitException ex)
{
op.Signal("rate-limit");
op.Signal($"rate-limit:{ex.RetryAfterMs}ms");
throw;
}
catch (TimeoutException)
{
op.Signal("timeout");
throw;
}
});
信号只是字符串。使用简单的名称("rate-limit"或结构化名称("rate-limit:5000ms").
模式过滤器使用 Glob 语义( Glob 语义) 。*, ?以及支持逗号清单("error.*,timeout"配对是决定性的,通过 StringPatternMatcher.
对于非常详细的可观测性,您可以在操作的每个阶段发出信号。图书馆包括一个样本。 信号提示服务 证明这一模式:
using Mostlylucid.Helpers.Ephemeral.Examples;
// Inside your work body where you have access to the operation's emitter:
await coordinator.ProcessAsync(async (request, op, ct) =>
{
var data = await SignalingHttpClient.DownloadWithSignalsAsync(
httpClient,
new HttpRequestMessage(HttpMethod.Get, request.Url),
op, // ISignalEmitter
ct);
// Process the downloaded data...
});
这在每个阶段发出信号:
何时发出信号?
|--------|------|
| stage.starting 在请求开始之前
| progress:0 初步进展标志
| stage.request * HTTP 请求已发送 * * * * HTTP 请求已发送 * * * * HTTP 请求已发送 * * HTTP 请求已发送 * * HTTP 请求已发送 * * HTTP 请求已发送 * HTTP 请求已发送 *
| stage.headers 收到回应信头
| stage.reading * 开始阅读正文 * * * 开始阅读正文 * * * 开始阅读正文 * * * 开始阅读正文 * * 开始阅读正文 * * * 开始阅读正文 * * * 开始阅读正文 * * 开始阅读正文 * * 开始阅读正文 * * 开始阅读正文 * * * 开始阅读正文 *
| progress:XX 下载期间的进度百分比(0-100)
| stage.completed * 下载完成 * * * * * * 下载完成 * * * * * * 下载完成 * * * * * * 下载完成 * * * * * * 下载完成 * * * * * * 下载完成 * * * * * * * 下载完成 * * * * * * * * 下载完成 * * * * * * * * * 下载完成 * * * * * * * * * *
然后,您可以用匹配的图案查询这些图案 :
// Find all stage transitions
var stages = coordinator.GetSignalsByPattern("stage.*");
// Check download progress
var progress = coordinator.GetSignalsByPattern("progress:*");
// Check if any download is still in progress
if (coordinator.HasSignalMatching("stage.reading") &&
!coordinator.HasSignalMatching("stage.completed"))
{
// Download in progress
}
操作也可以删除自己的信号。 这对临时状态有用 :
await coordinator.ProcessAsync(async (item, op, ct) =>
{
// Mark as processing
op.Emit("processing");
try
{
await ProcessItemAsync(item, ct);
// Success - retract the processing signal
op.Retract("processing");
op.Emit("completed");
}
catch (RetryableException)
{
// Keep processing signal, add retry info
op.Emit("retrying");
}
catch (Exception)
{
// Remove all temporary signals
op.RetractMatching("processing*");
op.Emit("failed");
throw;
}
});
就像信号发射一样,撤回可以触发回击:
var coordinator = new EphemeralWorkCoordinator<Request>(
body,
new EphemeralOptions
{
// Sync retraction handler
OnSignalRetracted = evt =>
{
_metrics.DecrementGauge(evt.Signal);
Console.WriteLine($"Signal {evt.Signal} retracted from op {evt.OperationId}");
if (evt.WasPatternMatch)
Console.WriteLine($" (matched pattern: {evt.Pattern})");
},
// Async retraction handler
OnSignalRetractedAsync = async (evt, ct) =>
{
await _telemetry.TrackRetraction(evt.Signal, evt.OperationId, ct);
}
});
缩略 SignalRetractedEvent 包括:
Signal - 撤回的信号名OperationId - 收回它的行动Key - 手术的钥匙(如果有的话)Timestamp - 发生撤回时WasPatternMatch - 如果通过 RetractMatchingPattern - 所用模式(如果模式匹配)await coordinator.ProcessAsync(async (request, op, ct) =>
{
// Check if we already have a rate limit signal
if (op.HasSignal("rate-limited"))
{
// We're in recovery mode
await Task.Delay(1000, ct);
}
try
{
var response = await _api.SendAsync(request, ct);
// Success! Remove any rate limit signal
if (op.Retract("rate-limited"))
{
op.Emit("rate-limit-cleared");
}
}
catch (RateLimitException ex)
{
op.Emit("rate-limited");
op.Emit($"rate-limit:{ex.RetryAfterMs}ms");
throw;
}
});
所有协调员都提供最佳信号查询:
// Check if any recent operation hit a rate limit
if (coordinator.HasSignal("rate-limit"))
{
await Task.Delay(1000);
}
// Count slow responses in the window
var slowCount = coordinator.CountSignals("slow-response");
if (slowCount > 10)
{
await ThrottleAsync();
}
// Get signals by pattern
var httpErrors = coordinator.GetSignalsByPattern("http.error.*");
// Get signals since a time
var recentSignals = coordinator.GetSignalsSince(DateTimeOffset.UtcNow.AddMinutes(-1));
// Get signals for a specific key
var userSignals = coordinator.GetSignalsByKey("user-123");
发自 时间选择cs:
协调员可对信号自动作出反应:
var coordinator = new EphemeralWorkCoordinator<Request>(
body,
new EphemeralOptions
{
// Cancel new work if these signals are present
CancelOnSignals = new HashSet<string> { "system-overload", "circuit-open" },
// Defer new work while these signals are present
DeferOnSignals = new HashSet<string> { "rate-limit" },
MaxDeferAttempts = 10,
DeferCheckInterval = TimeSpan.FromMilliseconds(100)
});
当信号在 CancelOnSignals 检测到,新项目被跳过(算作失败) 。
当信号在 DeferOnSignals 被检测到,新的项目将等待信号清除。
发自 适应性转换服务cs:
public class AdaptiveTranslationService : IAsyncDisposable
{
private readonly EphemeralWorkCoordinator<TranslationRequest> _coordinator;
private readonly ITranslationApi _translationApi;
public AdaptiveTranslationService(ITranslationApi translationApi)
{
_translationApi = translationApi;
_coordinator = new EphemeralWorkCoordinator<TranslationRequest>(
ProcessTranslationAsync,
new EphemeralOptions
{
MaxConcurrency = 8,
MaxTrackedOperations = 100,
// New work is deferred while any "rate-limit" or "rate-limit:*" signal is present
DeferOnSignals = new HashSet<string> { "rate-limit", "rate-limit:*" },
MaxDeferAttempts = 10,
DeferCheckInterval = TimeSpan.FromMilliseconds(100)
});
}
public async Task TranslateAsync(TranslationRequest request)
{
// Optional: extra politeness based on most recent retry-after
var rateLimitSignals = _coordinator.GetSignalsByPattern("rate-limit:*");
if (rateLimitSignals.Count > 0)
{
var latest = rateLimitSignals
.OrderByDescending(s => s.Timestamp)
.First()
.Signal; // "rate-limit:5000ms"
if (TryParseRetryAfter(latest, out var delay))
{
await Task.Delay(delay);
}
}
await _coordinator.EnqueueAsync(request);
}
public static bool TryParseRetryAfter(string signal, out TimeSpan delay)
{
delay = default;
var parts = signal.Split(':', 2);
if (parts.Length != 2) return false;
var payload = parts[1].Trim();
if (!payload.EndsWith("ms", StringComparison.OrdinalIgnoreCase)) return false;
var numPart = payload[..^2];
if (!int.TryParse(numPart, out var ms) || ms < 0) return false;
delay = TimeSpan.FromMilliseconds(ms);
return true;
}
}
当利率限制被击中时,这项服务的每个实例都会自动退缩。 没有共享状态, 没有消息传递。 正在读取时间窗口 。
多个协调员可以通过共同的交流相互感觉 SignalSink:
public class OrderProcessingSystem
{
private readonly SignalSink _sharedSignals = new(maxCapacity: 1000);
private readonly EphemeralWorkCoordinator<Order> _orderProcessor;
private readonly EphemeralWorkCoordinator<PaymentRequest> _paymentProcessor;
public OrderProcessingSystem()
{
var options = new EphemeralOptions { Signals = _sharedSignals };
_orderProcessor = new EphemeralWorkCoordinator<Order>(
ProcessOrderAsync, options);
_paymentProcessor = new EphemeralWorkCoordinator<PaymentRequest>(
ProcessPaymentAsync, options);
}
public async Task ProcessOrderAsync(Order order)
{
// Check shared signals for payment gateway issues
if (_sharedSignals.Detect("gateway-error"))
{
await _retryQueue.EnqueueAsync(order);
return;
}
await _orderProcessor.EnqueueAsync(order);
}
}
[HttpGet("/health/detailed")]
public IActionResult GetDetailedHealth()
{
return Ok(new
{
translation = new
{
pending = _translationCoordinator.PendingCount,
active = _translationCoordinator.ActiveCount,
recentRateLimits = _translationCoordinator.CountSignals("rate-limit"),
recentTimeouts = _translationCoordinator.CountSignals("timeout"),
recentSuccess = _translationCoordinator.CountSignals("success"),
hasErrors = _translationCoordinator.HasSignalMatching("error.*")
},
payment = new
{
pending = _paymentCoordinator.PendingCount,
gatewayErrors = _paymentCoordinator.CountSignals("gateway-error"),
declines = _paymentCoordinator.CountSignals("declined"),
approvals = _paymentCoordinator.CountSignals("approved")
}
});
}
不需要量度库。 只需查询时钟窗口 。
发自 基于信号的环形环形车列车cs:
public class SignalBasedCircuitBreaker
{
private readonly string _failureSignal;
private readonly int _threshold;
private readonly TimeSpan _windowSize;
public SignalBasedCircuitBreaker(
string failureSignal = "failure",
int threshold = 5,
TimeSpan? windowSize = null)
{
_failureSignal = failureSignal;
_threshold = threshold;
_windowSize = windowSize ?? TimeSpan.FromSeconds(30);
}
public bool IsOpen<T>(EphemeralWorkCoordinator<T> coordinator)
{
var recentFailures = coordinator.GetSignalsSince(
DateTimeOffset.UtcNow - _windowSize);
return recentFailures.Count(s => s.Signal == _failureSignal) >= _threshold;
}
public int GetFailureCount<T>(EphemeralWorkCoordinator<T> coordinator)
{
var recentFailures = coordinator.GetSignalsSince(
DateTimeOffset.UtcNow - _windowSize);
return recentFailures.Count(s => s.Signal == _failureSignal);
}
}
// Usage
var circuitBreaker = new SignalBasedCircuitBreaker("api-error", threshold: 3);
if (circuitBreaker.IsOpen(_coordinator))
{
throw new CircuitOpenException("Too many recent API errors");
}
await _coordinator.EnqueueAsync(request);
断路器没有自己的状态 它只读到时钟窗口
发自 信号cs:
当信号能引发其他信号时 你就会冒着无穷无尽的循环风险 SignalConstraints 防止:
var options = new EphemeralOptions
{
SignalConstraints = new SignalConstraints
{
// Max propagation depth before blocking
MaxDepth = 10,
// Prevent A → B → A cycles
BlockCycles = true,
// Signals that end propagation chains
TerminalSignals = new HashSet<string> { "completed", "failed", "resolved" },
// Signals that emit but don't propagate
LeafSignals = new HashSet<string> { "logged", "metric" },
// Callback when a signal is blocked
OnBlocked = (signal, reason) =>
{
_logger.LogWarning("Signal {Signal} blocked: {Reason}",
signal.Signal, reason);
}
}
};
音轨因果关系 EmitCaused:
public void HandleSignal(SignalEvent evt, ISignalEmitter emitter)
{
if (evt.Is("order-placed"))
{
// This signal carries the propagation chain
// Will be blocked if it would create a cycle
emitter.EmitCaused("inventory-reserved", evt.Propagation);
}
}
传播链跟踪路径: order-placed → inventory-reserved → ...
如果(如果) inventory-reserved 尝试发送 order-placed,就会被堵住(检测到循环)。
发自 信号cs:
为了让所有协调员都能看到需要显示的信号:
public sealed class SignalSink
{
private readonly ConcurrentQueue<SignalEvent> _window;
private readonly int _maxCapacity;
private readonly TimeSpan _maxAge;
public SignalSink(int maxCapacity = 1000, TimeSpan? maxAge = null);
// Raise signals
public void Raise(SignalEvent signal);
public void Raise(string signal, string? key = null);
// Sense signals
public IReadOnlyList<SignalEvent> Sense();
public IReadOnlyList<SignalEvent> Sense(Func<SignalEvent, bool> predicate);
public bool Detect(string signalName);
public bool Detect(Func<SignalEvent, bool> predicate);
public int Count { get; }
}
用法 :
// Create a shared sink
var sink = new SignalSink(maxCapacity: 1000, maxAge: TimeSpan.FromMinutes(2));
// Configure coordinators to use it
var options = new EphemeralOptions { Signals = sink };
// Or raise signals directly
sink.Raise("system-maintenance");
// Sense from anywhere
if (sink.Detect("system-maintenance"))
{
await DeferWorkAsync();
}
发自 书签父母:
用于信号过滤的球式匹配 :
// Exact match
coordinator.HasSignal("rate-limit");
// Wildcard patterns
coordinator.HasSignalMatching("http.*"); // http.timeout, http.error
coordinator.HasSignalMatching("error.*.critical"); // error.payment.critical
coordinator.HasSignalMatching("user-???-failed"); // user-123-failed
// Comma-separated patterns in CancelOnSignals/DeferOnSignals
new EphemeralOptions
{
CancelOnSignals = new HashSet<string>
{
"system-overload, circuit-open", // Either pattern
"error.*" // Any error signal
}
}
保持信号简单和一致:
// Good - simple, categorical
op.Signal("success");
op.Signal("failure");
op.Signal("rate-limit");
op.Signal("timeout");
op.Signal("cache-hit");
// Good - structured for parsing
op.Signal("rate-limit:5000ms");
op.Signal("retry:attempt-3");
op.Signal("slow:2500ms");
op.Signal("http.error:429");
// Good - hierarchical for pattern matching
op.Signal("payment.declined");
op.Signal("payment.approved");
op.Signal("payment.gateway-error");
// Avoid - entity identification belongs in Key, not signals
op.Signal("user-123-rate-limited"); // Bad
// Instead
op.Key = "user-123";
op.Signal("rate-limit");
同步信号处理器( C)OnSignal运行在操作的线条上, 让他们快速保持它们。 要完成 I/ O 任务, 将信号发送到同步路径 SignalDispatcher (模式匹配、确定性顺序)或 AsyncSignalProcessor.
await using var dispatcher = new SignalDispatcher(new EphemeralOptions
{
MaxConcurrency = Environment.ProcessorCount,
MaxConcurrencyPerKey = 1 // sequential per signal name by default
});
dispatcher.Register("error.*", evt => _alerts.SendAsync(evt.Signal));
dispatcher.Register("progress:*", evt => _metrics.Record(evt.Signal));
// In coordinator options, keep OnSignal chor options, keep OnSignal cheap and enqueue
var coordinator = new EphemeralWorkCoordinator<Request>(
body,
new EphemeralOptions
{
OnSignal = dispatcher.Dispatch
});
模式支助 *, ?和逗号列表("error.*,timeout"所有匹配的操作员均按登记顺序由背景钥匙协调员负责操作;发放时保持同步。
用于独立自动同步处理:
await using var processor = new AsyncSignalProcessor(
async (signal, ct) =>
{
await _externalService.LogAsync(signal, ct);
},
maxConcurrency: 4,
maxQueueSize: 1000);
// Enqueue signals (returns immediately)
processor.Enqueue(new SignalEvent(
"rate-limit",
operationId,
key,
DateTimeOffset.UtcNow));
将合成信号处理与遥测集成相结合的完整实例:
资料来源: 遥测信号Handler.cs
public class TelemetrySignalHandler : IAsyncDisposable
{
private readonly AsyncSignalProcessor _processor;
private readonly ITelemetryClient _telemetry;
public TelemetrySignalHandler(ITelemetryClient telemetry)
{
_telemetry = telemetry;
_processor = new AsyncSignalProcessor(
HandleSignalAsync,
maxConcurrency: 8,
maxQueueSize: 5000);
}
// Synchronous entry point - returns immediately
public bool OnSignal(SignalEvent signal) => _processor.Enqueue(signal);
private async Task HandleSignalAsync(SignalEvent signal, CancellationToken ct)
{
var properties = new Dictionary<string, string>
{
["signal"] = signal.Signal,
["operationId"] = signal.OperationId.ToString(),
["key"] = signal.Key ?? "none"
};
await _telemetry.TrackEventAsync("EphemeralSignal", properties, ct);
// Categorized tracking based on signal prefix
if (signal.StartsWith("error"))
await _telemetry.TrackExceptionAsync(signal.Signal, properties, ct);
else if (signal.StartsWith("perf"))
await _telemetry.TrackMetricAsync(signal.Signal, 1, ct);
}
// Expose stats for monitoring
public int QueuedCount => _processor.QueuedCount;
public long ProcessedCount => _processor.ProcessedCount;
public long DroppedCount => _processor.DroppedCount;
public async ValueTask DisposeAsync() => await _processor.DisposeAsync();
}
把它发给你的协调员:
await using var telemetryHandler = new TelemetrySignalHandler(telemetryClient);
await using var coordinator = new EphemeralWorkCoordinator<Request>(
ProcessAsync,
new EphemeralOptions
{
OnSignal = signal => telemetryHandler.OnSignal(signal)
});
处理器 :
OnSignal 立即返回信号是强大的 因为它们是 短时间:
财产 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 津贴 |----------|---------| | 环形 无法不受约束地成长 - 旧信号时代熄灭 - | 自我清洁 不需要清理代码 | 分离 光荣者不知道 听众 | 可观察 任何代码都能感知到目前的状况 | 私营 没有用户数据 - 只是信号名 | 快速快速 O(1)探测短路
时间窗口已经存在用于调试。 信号只是赋予它语义的含义 。
信号将孤立的处决原子变成 遥感网络每位协调员可以:
CancelOnSignals 和 DeferOnSignals没有信息代理,没有共享状态,没有协调协议,只有使用自然衰减的元数据操作。
原子不会直接和对方交谈, 它们只是把痕迹留在窗口里, 其他人可以观察它们。 它对于合成系统来说是一丝不苟的。
火 信号 感知 忘记
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.