异步等待已运行任务:全局资源懒加载避免重复数据库调用的实现咨询
解决全局资源懒加载的并发重复调用问题
你遇到的这个场景很典型——并发触发懒加载逻辑时,重复执行了数据库查询,浪费了资源。你的思路方向完全正确:通过缓存初始化任务,让后续请求等待同一个正在运行的任务,而不是重复执行初始化逻辑。下面是适配async/await模式的正确实现,完全不会有手动操作Task的冲突问题:
核心思路
我们用一个Task类型的字段来缓存初始化任务,替代单纯的_isLoaded标记:
- 第一个请求进入时,创建并启动初始化任务,将其存入字段
- 后续请求进来时,直接等待这个已存在的任务完成
- 利用
async方法的天然特性,不需要手动调用Task.Start()——async方法返回的Task本身就是"热任务",调用方法时内部逻辑就已经开始执行了
修改后的GlobalResources实现
public class GlobalResources { // 缓存初始化任务,null表示未开始加载 private Task<string>? _loadTask; private readonly object _loadLock = new object(); private string _externalQueryResult = string.Empty; public async Task LoadAsync() { // 快速检查,避免不必要的锁竞争 if (_loadTask != null) { await _loadTask; return; } lock (_loadLock) { // 双重检查,确保只有第一个请求会创建初始化任务 if (_loadTask != null) { await _loadTask; return; } // 仅第一次进入时执行,创建并启动数据库查询任务 _loadTask = LongRunningQuery(); } // 等待初始化任务完成 await _loadTask; // 将查询结果赋值到字段(如果需要后续使用) _externalQueryResult = await _loadTask; } public async Task<string> LongRunningQuery() { Console.WriteLine("Loading data from Database ( this line should only be written once on console window)."); await Task.Delay(5000); return "123"; } }
关键细节解释
双重检查锁的正确用法:
- 外层的快速检查避免了每次请求都进入锁,提升并发场景下的性能
- 内层锁里的二次检查,彻底防止多个并发请求同时创建初始化任务
async任务的特性适配:- 调用
LongRunningQuery()时,方法内部的代码会立即开始执行,返回的Task代表这个正在运行的操作 - 后续请求
await _loadTask时,会自动等待同一个任务完成,不会触发新的数据库调用
- 调用
可选的异常处理:
如果你的初始化逻辑可能失败,且希望后续请求可以重试,可以在任务完成后判断状态,若失败则重置任务:try { await _loadTask; _externalQueryResult = await _loadTask; } catch { // 加载失败,重置任务让下一次请求可以重试 lock (_loadLock) { _loadTask = null; } throw; // 将异常抛出给调用方处理 }
测试验证
运行你原有的测试代码,现在控制台只会输出一次Loading data from Database...,两个MyToolClass的InitAsync都会等待同一个任务完成,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Alois
相关产品推荐
相关产品推荐

