C#中重复调用API并并发处理数据的问题排查
C#分页API并发处理问题排查与修复
问题场景
调用分页API时,期望获取当前数据块后立即处理,同时请求下一页数据,但当前代码是串行执行:等待API返回→处理数据→再请求下一页,完全没实现并发效果。
原代码
JArray jsonObjects = new JArray(); Task.Run(async () => { jsonObjects = await GetObjectsJSONAsync(); }).GetAwaiter().GetResult();
private async Task<JArray> GetObjectsJSONAsync() { /* * HttpClient setup here */ var response = ""; var headers = ""; var total = 0; JArray productsFromURL = new JArray(); HttpResponseMessage result = await client.GetAsync("/products/skus/"); if (result.IsSuccessStatusCode) { headers = result.Headers.GetValues("Link").FirstOrDefault(); response = await result.Content.ReadAsStringAsync(); jsonObjectsFromURL = JArray.Parse(response); ProcessObjects(productsFromURL); // 这里存在bug:传了空的productsFromURL,应该传jsonObjectsFromURL while (headers.Contains("rel=\"next\"")) //while header contains next link { var url = headers.Split(';')[0].Replace("<", "").Replace(">", ""); result = await client.GetAsync(url); try { response = await result.Content.ReadAsStringAsync(); jsonObjectsFromURL = JArray.Parse(response); ProcessObjects(jsonObjectsFromURL); //adds processed objects to a ConcurrentBag headers = result.Headers.GetValues("Link").FirstOrDefault(); } catch (Exception e) { //This means that there are no more pages to get headers = ""; } } } return jsonObjectsFromURL; //dont care about the return here }
问题分析
原代码的核心问题是所有操作串行执行:
- 先await请求当前页,等待返回
- 处理当前页数据
- 再await请求下一页,循环往复
整个流程中,处理数据和请求下一页完全没有并行,浪费了等待API响应的时间。
另外原代码还有个明显bug:第一页处理时传了空的productsFromURL,应该传解析后的jsonObjectsFromURL。
修复方案
要实现处理当前页和请求下一页的并发,核心思路是让数据处理和下一页请求同时进行:发起下一页请求的同时,处理当前页的数据,利用等待API响应的时间来处理数据。
修改后的代码如下:
// 外层调用简化,无需接收返回值 Task.Run(async () => { await GetObjectsJSONAsync(); }).GetAwaiter().GetResult();
private async Task GetObjectsJSONAsync() // 不需要返回值,结果存入ConcurrentBag { /* HttpClient setup here */ HttpResponseMessage result = await client.GetAsync("/products/skus/"); if (!result.IsSuccessStatusCode) return; var headers = result.Headers.GetValues("Link").FirstOrDefault(); var response = await result.Content.ReadAsStringAsync(); var currentPageData = JArray.Parse(response); // 启动当前页的处理任务,不等待完成 Task processingTask = Task.Run(() => ProcessObjects(currentPageData)); while (headers?.Contains("rel=\"next\"") == true) { // 解析下一页URL,先发起下一页请求(不await,让请求与当前页处理并行) var nextUrl = headers.Split(';')[0].Trim('<', '>'); Task<HttpResponseMessage> nextPageRequestTask = client.GetAsync(nextUrl); // 等待当前页处理完成(若允许处理任务后台持续执行,也可跳过此等待,依业务需求而定) await processingTask; // 等待下一页请求结果 result = await nextPageRequestTask; if (!result.IsSuccessStatusCode) break; // 解析下一页数据 response = await result.Content.ReadAsStringAsync(); currentPageData = JArray.Parse(response); headers = result.Headers.GetValues("Link").FirstOrDefault(); // 启动下一页的处理任务 processingTask = Task.Run(() => ProcessObjects(currentPageData)); } // 等待最后一页的处理任务完成,避免程序退出时数据未处理完毕 await processingTask; }
关键修改点
- 并发执行请求与处理:在处理当前页的同时发起下一页的API请求,充分利用等待响应的时间
- 修正参数错误:修复了第一页处理时传空数组的bug
- 移除无用返回值:原返回值未被使用,直接删除,简化方法签名
- 确保最后一页处理完成:循环结束后等待最后一页的处理任务,避免数据丢失
内容的提问来源于stack exchange,提问作者mycsacc
相关产品推荐
相关产品推荐

