ASP.NET Core中用工厂模式构造带数值参数的gRPC拦截器
解决方案与建议
一、更优雅的键控服务注册方案
ASP.NET Core 3.1+ 提供的键控服务(Keyed Services) 正好适配你这种需要按参数区分实例的场景,完全可以替代自定义工厂池的实现,更符合框架原生设计:
1. 注册带键的拦截器单例
直接通过AddKeyedSingleton为不同参数值的MyInterceptor注册带标识的单例:
// 注册参数为1的拦截器,键直接使用参数值作为标识 builder.Services.AddKeyedSingleton<MyInterceptor>(1, sp => new MyInterceptor(1)); // 注册参数为10的拦截器 builder.Services.AddKeyedSingleton<MyInterceptor>(10, sp => new MyInterceptor(10));
2. 为gRPC客户端绑定对应键的拦截器
添加拦截器时,通过服务提供器的GetRequiredKeyedService方法按键获取实例:
builder.Services .AddGrpcClient<MyService.MyServiceClient>() .AddInterceptor(sp => sp.GetRequiredKeyedService<MyInterceptor>(10));
这种方式的优势:
- 无需自定义工厂类,由DI容器统一管理实例生命周期与复用
- 代码更简洁,符合ASP.NET Core的原生开发模式
- 天然支持线程安全,DI容器的单例实例本身就是线程安全的(只要你的
MyInterceptor是线程安全的)
如果你的拦截器参数是动态生成的,也可以结合工厂模式与键控服务,比如注册一个键控工厂来动态创建,但对于固定参数值的场景,直接注册带键的单例是最优解。
二、关于Dictionary与ConcurrentDictionary的选择
你的自定义MyInterceptorPool中必须使用ConcurrentDictionary,原因如下:
- ASP.NET Core应用在运行时是多线程环境,多个请求线程可能同时调用
GetOrAddMyInterceptor方法 Dictionary并非线程安全,并发执行TryGetValue+Add会导致数据竞争,可能抛出异常或出现数据损坏ConcurrentDictionary的GetOrAdd方法是原子操作,天然支持线程安全,且代码更简洁
优化后的MyInterceptorPool实现:
public class MyInterceptorPool { private readonly ConcurrentDictionary<int, MyInterceptor> _pool = new(); public MyInterceptor GetOrAddMyInterceptor(int parameter) { // 直接使用ConcurrentDictionary的原子GetOrAdd方法 return _pool.GetOrAdd(parameter, p => new MyInterceptor(p)); } }
总结
优先推荐使用键控服务方案,它是框架原生支持的特性,无需额外维护自定义池;如果必须使用自定义工厂池,一定要替换为ConcurrentDictionary保证线程安全。
内容的提问来源于stack exchange,提问作者Slepoyi
相关产品推荐
相关产品推荐

