应用启动时仅调用一次CarService获取数据并全局复用的实现方案问询
解决方案
要实现应用启动时仅调用一次CarService.GetData(),并将结果通过DI共享给所有消费者,可以通过两种方案实现,以下是具体实现细节:
方案1:改造CarService实现内部缓存(懒加载)
让单例的CarService在首次调用GetData()时加载数据并缓存,后续调用直接返回缓存结果,避免重复请求。这种方式无需额外HostedService,实现简单。
修改后的CarService代码
public interface ICarService { Task<List<Car>> GetData(); } public class CarService : ICarService { private readonly IHttpClientFactory _httpClientFactory; private List<Car> _cachedCars; // 用信号量防止并发场景下重复发起请求 private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public CarService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task<List<Car>> GetData() { // 已缓存则直接返回 if (_cachedCars != null) return _cachedCars; await _semaphore.WaitAsync(); try { // 双重检查,避免等待信号量期间数据已被其他线程加载 if (_cachedCars != null) return _cachedCars; var client = _httpClientFactory.CreateClient(); var req = new HttpRequestMessage(HttpMethod.Get, "https://localhost:5011/cars"); var res = await client.SendAsync(req); res.EnsureSuccessStatusCode(); // 确保请求成功 // 假设使用System.Text.Json序列化,可根据实际调整 var data = await res.Content.ReadFromJsonAsync<List<Car>>(); _cachedCars = data ?? new List<Car>(); return _cachedCars; } finally { _semaphore.Release(); } } }
Program.cs注册
// 保持单例注册即可,无需HostedService services.AddSingleton<ICarService, CarService>();
方案2:启动时预加载数据(主动加载)
通过IHostedService在应用启动阶段主动调用GetData(),将结果注册为单例,后续消费者直接注入缓存数据或封装后的服务。这种方式确保数据在应用启动完成后就已准备好,无首次请求延迟。
步骤1:定义缓存数据服务
// 封装缓存数据的服务,方便消费者注入 public interface ICachedCarService { List<Car> GetCachedCars(); } public class CachedCarService : ICachedCarService { private readonly List<Car> _cars; public CachedCarService(List<Car> cars) { _cars = cars; } public List<Car> GetCachedCars() { return _cars; } }
步骤2:实现预加载HostedService
public class PreloadCarsHostedService : IHostedService { private readonly IServiceProvider _serviceProvider; public PreloadCarsHostedService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public async Task StartAsync(CancellationToken cancellationToken) { using var scope = _serviceProvider.CreateScope(); var carService = scope.ServiceProvider.GetRequiredService<ICarService>(); var cars = await carService.GetData(); // 将加载好的汽车列表注册为单例 var serviceCollection = _serviceProvider.GetRequiredService<IServiceCollection>(); serviceCollection.AddSingleton(cars); serviceCollection.AddSingleton<ICachedCarService, CachedCarService>(); } public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } }
步骤3:Program.cs注册
services.AddSingleton<ICarService, CarService>(); services.AddHostedService<PreloadCarsHostedService>();
消费者使用示例
public class CarsController : ControllerBase { private readonly ICachedCarService _cachedCarService; public CarsController(ICachedCarService cachedCarService) { _cachedCarService = cachedCarService; } [HttpGet] public IActionResult GetCars() { var cars = _cachedCarService.GetCachedCars(); return Ok(cars); } }
方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 方案1(懒加载) | 代码简洁,无需额外服务 | 首次调用GetData()会有请求延迟 |
| 方案2(预加载) | 数据在启动阶段完成加载,无首次请求延迟 | 需要额外定义服务,代码稍复杂 |
可根据实际场景选择合适的方案。
内容的提问来源于stack exchange,提问作者user1765862
相关产品推荐
相关产品推荐

