.NET Core 6 HttpClient基于路径的重复并行请求处理咨询
解决.NET Core 6中并行重复API请求的问题
针对你的场景,国内可以关注博客园、掘金的.NET技术板块,国外Stack Overflow本身就是非常合适的.NET技术社区。下面是你要的两种方案实现:
方案1:基于路径检测重复请求,等待首个请求完成后再查缓存
核心思路是用并发安全字典+信号量标记正在处理的请求路径,让后续相同路径的请求等待首个请求完成并写入缓存后,再从缓存读取结果,避免重复调用上游API。
实现代码
using System.Collections.Concurrent; using Microsoft.Extensions.Caching.Memory; public class ApiRequestHandler { private readonly HttpClient _httpClient; private readonly IMemoryCache _cache; // 存储每个请求路径对应的信号量,控制并发等待 private readonly ConcurrentDictionary<string, SemaphoreSlim> _requestLocks = new(); public ApiRequestHandler(HttpClient httpClient, IMemoryCache cache) { _httpClient = httpClient; _cache = cache; } public async Task<HttpResponseMessage> GetApiResponseAsync(string requestPath) { // 先尝试从缓存读取 if (_cache.TryGetValue(requestPath, out HttpResponseMessage cachedResponse)) { return cachedResponse; } // 获取当前路径的信号量,不存在则创建(初始计数1) var semaphore = _requestLocks.GetOrAdd(requestPath, _ => new SemaphoreSlim(1, 1)); try { await semaphore.WaitAsync(); // 再次检查缓存:避免首个请求完成后,后续等待的请求重复调用API if (_cache.TryGetValue(requestPath, out cachedResponse)) { return cachedResponse; } // 调用上游API var response = await _httpClient.GetAsync(requestPath); response.EnsureSuccessStatusCode(); // 写入缓存(过期时间按需调整) _cache.Set(requestPath, response, TimeSpan.FromMinutes(5)); return response; } finally { semaphore.Release(); // 可选:请求完成后移除信号量,避免字典无限增长 _requestLocks.TryRemove(requestPath, out _); } } }
关键点说明
ConcurrentDictionary保证多线程下安全访问信号量- 两次检查缓存:第一次进入方法时,第二次获取信号量后,防止首个请求写入缓存后,后续请求仍重复调用API
SemaphoreSlim.WaitAsync()实现异步等待,不会阻塞线程
方案2:返回首个请求的响应任务(类似Java Future)
核心思路是把正在执行的请求任务本身存入并发字典,后续相同请求直接await这个任务,自动复用首个请求的响应结果,无需修改现有缓存逻辑,侵入性极低。
实现代码
using System.Collections.Concurrent; using Microsoft.Extensions.Caching.Memory; public class ApiRequestHandler { private readonly HttpClient _httpClient; private readonly IMemoryCache _cache; // 存储每个请求路径对应的请求任务 private readonly ConcurrentDictionary<string, Task<HttpResponseMessage>> _pendingRequests = new(); public ApiRequestHandler(HttpClient httpClient, IMemoryCache cache) { _httpClient = httpClient; _cache = cache; } public async Task<HttpResponseMessage> GetApiResponseAsync(string requestPath) { // 先尝试从缓存读取 if (_cache.TryGetValue(requestPath, out HttpResponseMessage cachedResponse)) { return cachedResponse; } // 尝试获取或创建请求任务 var requestTask = _pendingRequests.GetOrAdd(requestPath, async path => { try { var response = await _httpClient.GetAsync(path); response.EnsureSuccessStatusCode(); // 写入缓存 _cache.Set(path, response, TimeSpan.FromMinutes(5)); return response; } finally { // 请求完成后移除任务,避免内存泄漏 _pendingRequests.TryRemove(path, out _); } }); // 等待任务完成,不管是自己创建的还是复用的 return await requestTask; } }
关键点说明
ConcurrentDictionary<string, Task<HttpResponseMessage>>存储正在执行的请求任务,后续请求直接复用- 任务完成后自动移除,避免字典无限增长
- 完全兼容现有缓存逻辑,只需包装请求调用部分,几乎不需要改动原有业务实现
内容的提问来源于stack exchange,提问作者Prasad.CH
相关产品推荐
相关产品推荐

