如何异步初始化多线程共享的ISomething资源且避免重复构造?
用AsyncLazy完美解决你的异步共享初始化问题
绝对推荐用AsyncLazy来搞定这个场景——这简直是为你的需求量身定做的方案!
你说得对,普通的lock锁在异步代码里确实不是好选择:它会直接阻塞线程,浪费线程池资源,完全违背了异步编程“不阻塞、高利用率”的初衷。而AsyncLazy正好解决了异步延迟初始化+全局共享+非阻塞等待这几个核心痛点。
为什么AsyncLazy是最优解?
AsyncLazy的核心逻辑就是:
- 只有第一次被访问时,才会触发你的初始化逻辑(调用Web服务构造
ISomething) - 所有后续请求都会异步等待同一个初始化
Task完成,不会重复执行构造代码 - 全程是非阻塞的,线程可以去处理其他请求,直到初始化完成再回来继续
具体实现步骤
1. 定义AsyncLazy(自己实现或用第三方库)
如果不想引入第三方包,可以自己写一个极简版的AsyncLazy:
public class AsyncLazy<T> { private readonly Lazy<Task<T>> _lazyTask; public AsyncLazy(Func<Task<T>> factory) { _lazyTask = new Lazy<Task<T>>(factory); } public Task<T> Value => _lazyTask.Value; // 支持直接await AsyncLazy实例 public TaskAwaiter<T> GetAwaiter() => Value.GetAwaiter(); }
也可以用成熟的第三方库Nito.AsyncEx里的AsyncLazy,它提供了更多配置选项(比如容错重试),默认行为就是只初始化一次。
2. 注册Singleton的AsyncLazy实例
因为要所有客户端线程共享,必须把AsyncLazy<ISomething>注册为Singleton:
services.AddSingleton(new AsyncLazy<ISomething>(async () => { // 这里写你的Web服务调用+ISomething构造逻辑 var httpClient = services.BuildServiceProvider().GetRequiredService<HttpClient>(); var apiResponse = await httpClient.GetAsync("https://your-service-url/api/data"); apiResponse.EnsureSuccessStatusCode(); var dto = await apiResponse.Content.ReadFromJsonAsync<YourDataDto>(); return new SomethingImplementation(dto); })); // 你的Scoped Worker正常注册 services.AddScoped<Worker>();
3. 在Scoped Worker中使用AsyncLazy
在每个请求创建的Worker里,注入AsyncLazy<ISomething>,然后直接await获取实例:
public class Worker { private readonly AsyncLazy<ISomething> _sharedSomething; public Worker(AsyncLazy<ISomething> sharedSomething) { _sharedSomething = sharedSomething; } public async Task ProcessRequestAsync() { // 异步等待初始化完成,获取全局共享的ISomething实例 var something = await _sharedSomething; // 接下来用something处理业务逻辑 await something.DoBusinessLogicAsync(); } }
关键注意事项
- 必须确保
AsyncLazy<ISomething>是Singleton,否则每个Scoped Worker会拿到不同的实例,导致重复初始化 - 初始化逻辑里如果需要依赖其他服务(比如HttpClient),要正确从服务提供者获取,避免生命周期问题
- 全程用
await等待,绝对不要调用Result或Wait(),否则会回到阻塞线程的老问题
内容的提问来源于stack exchange,提问作者Shadow
相关产品推荐
相关产品推荐

