agilelabs-fx-docs main workcontexts/ability.md

WorkContext 能力概览

本页总结具体能力和源码行为。跨 MVC、WebAPI、Job、Task 的统一结论,请优先阅读 WorkContext

WorkContext(工作上下文)是 AgileLabs Framework 中的业务执行单元,贯穿 HTTP 请求、后台任务、Hangfire Job 以及 Task.Run 等场景。本页基于源码总结可用能力与最佳实践。

术语与适用场景

  • HTTP 请求WebWorkContextInitMiddleware 在请求进入时创建 WorkContext 并绑定 IServiceScope
  • 后台任务 / Job:在 BackgroundService、Hangfire Job 等场景需手动创建 Scope 以保证日志与依赖可用。
  • 并发 Task:在 Task.Run 或线程池任务中必须调用 CreateScopeWithWorkContextForNewTask(),避免复用父线程的 AsyncLocal 数据。

核心属性一览

属性 类型/来源 用途
ServiceProvider Scoped 容器 解析服务(非 Root),Scope 结束后失效。
Mapper IMapper AutoMapper 映射。
Items IDictionary<object, object> 可继承缓存,适合轻量共享对象。
TempItems IDictionary<object, object> 不会传递到子上下文,用于一次性状态。
Identity IIdentityInfo 当前用户/租户信息。
LogTransId string 日志 TraceId,与 Activity、IRequestSession.Tid 对齐。
WorkContextCultureInfo / WorkContextTimeZoneInfo CultureInfo/TimeZoneInfo 包装 控制本地化、时区。
Activity System.Diagnostics.Activity 框架自动创建,记录 TraceId/SpanId。

Items 与临时缓存

  • Items 是否继承取决于 PropertiesInheritFlag.Items,默认继承父 WorkContext 的引用。若对象不可共享,可在创建子 Scope 时使用 PropertiesAssignMode.DeepClone
  • TempItems 永远不会流向子上下文,适合存放仅当前 Scope 有效的数据,例如一次性令牌。
  • 建议:为 Items 的 Key 使用常量,避免把大型或非线程安全对象放入其中;必要时可自定义类型保护并发。

Scope 创建与继承

// 后台任务中创建独立 WorkContext
await Task.Run(() =>
{
    using var scope = AgileLabContexts.Context.CreateScopeWithWorkContextForNewTask(
        inheritFlag: (uint)PropertiesInheritFlag.FrameworkProperties,
        assignMode: PropertiesAssignMode.DeepClone,
        scopeName: "BackgroundSync");

    scope.WorkContext.SetName("SyncOrders");
    var service = scope.WorkContext.ServiceProvider.GetRequiredService<OrderSyncService>();
    service.Execute();
});
  • CreateScopeWithWorkContext():在当前线程创建子上下文,默认复用 WorkContextHolder
  • CreateScopeWithWorkContextForNewTask():强制创建新的 Holder,用于 Task/线程。
  • PropertiesInheritFlag:控制继承内容。常用标记:IdentityLogTransIdCultureInfoTimeZoneItemsFrameworkProperties 包含框架级属性,All 是默认值。
  • PropertiesAssignModeReference(共享引用)、DeepClone(表达式树深拷贝)、DeepCloneByJsonSerialize(序列化)。根据线程安全需求选择。
  • 所有新 Scope 都从 RootServiceProvider 创建,因此哪怕父 Scope Dispose,也不影响子 Scope。
  • WorkContextScope.Dispose() 会清空 Items、释放 Activity,并在必要时恢复上一层 WorkContext。

诊断与监控

  • WorkContextTraceTable.Instance.WorkContextCreatedEvent / WorkContextDisposingEvent:订阅可记录自定义指标或追踪泄漏。
  • GetWorkContexts(predicate, qpsCount):查看当前存活 WorkContext 与最近 QPS,支持过滤条件。
  • SetName(string):为 WorkContext 命名,例如 OrderCheckout,便于 TraceTable 与日志识别业务来源。
  • 调试日志中会输出 WorkContextScope with WorkContextId:#... Created,可确认 Scope 生命周期。

与其他组件协作

  • ActivityDefaultWorkContextCore 在创建时会生成 Activity,AppBootstrapperIRequestSession 使用相同 TraceId,便于分布式追踪。
  • HTTP 管道WebWorkContextInitMiddlewareUseRouting 之前初始化 WorkContext 并关联 IRequestSession 属性。
  • HangfireWorkContextJobActivator(见 AgileLabs.WebApp.Hangfires)确保 Job 执行时也存在 WorkContext。
  • 后台服务:在 BackgroundService.ExecuteAsync 内部使用 CreateScopeWithWorkContextForNewTask() 包装每次执行逻辑。

示例

控制器读取上下文信息

public class ProfileController : ControllerBase
{
    private readonly IWorkContextCore _workContext;
    public ProfileController(IWorkContextCore workContext) => _workContext = workContext;

    [HttpGet("/me/profile")]
    public object Get() => new
    {
        User = _workContext.Identity,
        Culture = _workContext.CultureInfo.Name,
        TimeZone = _workContext.TimeZoneInfo.Id,
        TraceId = _workContext.LogTransId
    };
}

后台任务创建 Scope(见上方代码),可结合 SetName 与日志定位。

最佳实践

  1. 命名每个自建 Scope:调用 SetName,帮助诊断。
  2. 禁止缓存 ServiceProvider:Scope 结束后引用失效,应通过 DI 注入所需服务。
  3. 谨慎使用 Items:共享引用需考虑线程安全,必要时改用深拷贝或独立缓存。
  4. 后台/并发场景务必创建 Scope:直接在静态方法解析服务会缺失 WorkContext,导致日志链路断裂。
  5. 监控 TraceTable:在压测或排障时订阅事件,确保 WorkContext 能及时释放。

延伸阅读

更多实现细节请参考源码 agilelabs.aspnet/src/AgileLabs.WebApp/WorkContexts