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:控制继承内容。常用标记:Identity、LogTransId、CultureInfo、TimeZone、Items。FrameworkProperties包含框架级属性,All是默认值。PropertiesAssignMode:Reference(共享引用)、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 生命周期。
与其他组件协作
- Activity:
DefaultWorkContextCore在创建时会生成 Activity,AppBootstrapper和IRequestSession使用相同 TraceId,便于分布式追踪。 - HTTP 管道:
WebWorkContextInitMiddleware在UseRouting之前初始化 WorkContext 并关联IRequestSession属性。 - Hangfire:
WorkContextJobActivator(见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 与日志定位。
最佳实践
- 命名每个自建 Scope:调用
SetName,帮助诊断。 - 禁止缓存
ServiceProvider:Scope 结束后引用失效,应通过 DI 注入所需服务。 - 谨慎使用
Items:共享引用需考虑线程安全,必要时改用深拷贝或独立缓存。 - 后台/并发场景务必创建 Scope:直接在静态方法解析服务会缺失 WorkContext,导致日志链路断裂。
- 监控 TraceTable:在压测或排障时订阅事件,确保 WorkContext 能及时释放。
延伸阅读
更多实现细节请参考源码 agilelabs.aspnet/src/AgileLabs.WebApp/WorkContexts。