ASP.NET Core 问答 04:后台任务、实时通信、测试与可观测性
061 BackgroundService.ExecuteAsync 应如何退出?
难度: 基础
查看参考答案
结论: 持续监听 stoppingToken,在取消后停止取新任务并完成有界清理
原因: 宿主停止依赖协作式取消,忽略令牌会拖延关机
边界: 长阻塞调用需使用可取消 API 或设置超时
最小示例: while (!stoppingToken.IsCancellationRequested)
062 为什么不能把重要后台任务直接 Task.Run 后丢弃?
难度: 基础
查看参考答案
结论: 应把任务交给受宿主管理的队列或持久化作业系统
原因: fire-and-forget 无法可靠观察异常、重试和关机完成状态
边界: 请求作用域结束后捕获的 scoped 依赖还可能已释放
最小示例: 端点只入队任务 ID,由 Hosted Service 消费
063 如何设计有界后台队列?
难度: 基础
查看参考答案
结论: 使用有容量的 Channel 并明确满载时等待、拒绝或丢弃策略
原因: 无界队列在流量峰值会耗尽内存,有界队列提供背压
边界: 队列只是进程内机制,重启不保证任务不丢
最小示例: Channel.CreateBounded 配合写入超时
064 周期任务如何避免重叠执行?
难度: 基础
查看参考答案
结论: 串行等待每次任务完成,或使用互斥和跳过策略明确控制重叠
原因: 定时触发频率高于处理耗时时会累积并发
边界: 多实例部署还需分布式租约或幂等设计
最小示例: PeriodicTimer 循环中 await 本轮工作
065 后台任务如何处理重试?
难度: 基础
查看参考答案
结论: 只重试瞬时故障,使用退避、抖动、次数上限和死信或告警
原因: 无限快速重试会放大故障,非幂等操作可能产生重复副作用
边界: 重试前要判断异常类型并使用幂等键
最小示例: 数据库超时退避重试,参数错误直接失败
066 SignalR Hub 生命周期有什么特点?
难度: 基础
查看参考答案
结论: Hub 实例通常按调用临时创建,不应把连接状态保存在 Hub 字段
原因: 跨调用状态应放外部存储或按连接管理的服务
边界: 多节点还需 backplane 或托管服务广播跨节点消息
最小示例: 用 Clients.User 向用户连接发送
067 SignalR Group 能否作为安全边界?
难度: 进阶
查看参考答案
结论: 不能,加入组前仍需认证授权,组成员关系是路由便利机制
原因: 组名和客户端参数可被操纵,重连后成员关系也需恢复
边界: 发送敏感消息前应重新依据可信身份判断
最小示例: 服务端根据授权结果 AddToGroupAsync
068 SignalR 如何应对慢客户端?
难度: 进阶
查看参考答案
结论: 限制消息大小和缓冲、传播取消,并监控积压与断开
原因: 无限缓存会让单个慢连接消耗大量内存
边界: 不同传输和部署拓扑会影响背压行为
最小示例: 对高频更新合并或丢弃过期状态
069 gRPC Deadline 与取消如何传播?
难度: 进阶
查看参考答案
结论: 客户端设置 Deadline,服务端读取 ServerCallContext.CancellationToken 并传给下游
原因: 超时信号不会强制终止忽略取消的代码
边界: 已提交的外部副作用不会自动回滚,需要幂等与补偿
最小示例: 数据库查询接收调用取消令牌
070 gRPC 流式调用要注意哪些并发约束?
难度: 进阶
查看参考答案
结论: 单个流的读写通常遵守单读单写,并处理背压、取消和中途失败
原因: 任意并发写同一响应流可能异常或乱序
边界: 长流还要限制消息大小和连接持续时间
最小示例: 用单写者通道汇聚多个生产者
071 WebApplicationFactory 能覆盖哪些集成行为?
难度: 进阶
查看参考答案
结论: 它启动测试宿主,可覆盖路由、中间件、认证、序列化和服务组合
原因: 它比直接 new Controller 更接近真实请求,但外部依赖仍需隔离
边界: 测试配置必须避免连接生产资源
最小示例: factory.CreateClient 发送 HTTP 请求
072 测试中如何替换认证?
难度: 进阶
查看参考答案
结论: 注册专用 AuthenticationHandler 产生确定 Claims,并分别测试 401、403 和成功路径
原因: 全局绕过授权会掩盖缺失策略和端点元数据错误
边界: 替身身份必须与生产策略使用的 Claim 契约一致
最小示例: 测试处理器返回指定用户
073 为什么 EF Core 查询常需要真实数据库集成测试?
难度: 进阶
查看参考答案
结论: 内存集合无法验证 SQL 翻译、排序、空值和数据库特定行为
原因: Mock DbSet 容易复制 LINQ to Objects 语义而产生假阳性
边界: 可以用容器化目标数据库或隔离测试库
最小示例: 验证复杂 GroupBy 生成的 SQL
074 单元测试为什么不应依赖 Thread.Sleep?
难度: 进阶
查看参考答案
结论: Sleep 依赖机器时序且拖慢测试,应注入 TimeProvider 或可控信号
原因: CI 负载变化会让固定等待产生随机失败
边界: 真正的超时集成测试仍需合理上限和诊断
最小示例: FakeTimeProvider 推进虚拟时间
075 日志、指标和追踪分别回答什么问题?
难度: 实战
查看参考答案
结论: 日志记录离散事件,指标展示聚合趋势,追踪连接一次请求的跨服务路径
原因: 三者互补,单独堆日志很难解释延迟分布和依赖链
边界: 高基数标签会增加指标成本,敏感数据不应进入遥测
最小示例: traceId 关联日志与 span
076 OpenTelemetry 在 ASP.NET Core 中如何接入?
难度: 实战
查看参考答案
结论: 注册 Activity、Meter 和日志导出器,并配置资源与采样
原因: 自动仪表化提供基础跨度,自定义业务步骤仍需显式埋点
边界: 导出失败不能阻塞核心请求,采样会影响可见细节
最小示例: 为支付调用创建自定义 ActivitySource
077 Correlation ID 与 Trace ID 有什么关系?
难度: 实战
查看参考答案
结论: Trace ID 是分布式追踪标准上下文,业务 Correlation ID 可作为额外关联字段
原因: 优先传播标准 W3C traceparent,避免每个服务自造不兼容头
边界: 外部传入关联值要校验长度和字符,防止日志注入
最小示例: 日志作用域加入 TraceId
078 健康检查应区分哪些状态?
难度: 实战
查看参考答案
结论: Liveness 判断进程是否应重启,Readiness 判断实例能否接流量
原因: 把所有依赖抖动都判为不存活会触发重启风暴
边界: 检查必须有超时并避免高成本查询
最小示例: 数据库不可用时 readiness 失败但进程仍存活
079 生产异常应该记录几次?
难度: 实战
查看参考答案
结论: 在能够处理或最终边界记录一次,并携带结构化上下文
原因: 每层捕获后都记录再抛出会产生重复噪声
边界: 若某层增加独有诊断,可用作用域或异常数据而不是重复整段堆栈
最小示例: 全局异常处理器记录未处理异常
080 如何诊断偶发高延迟?
难度: 实战
查看参考答案
结论: 结合延迟分位数、分布式追踪、运行时计数器、数据库和依赖指标定位阶段
原因: 平均值会隐藏尾延迟,单次日志也无法说明总体频率
边界: 诊断工具本身有成本,应先低开销观测再针对性采样
最小示例: 从 P99 慢请求 trace 下钻 SQL span