如何实现多线程并行发送带MaterialID的API Post请求并保存响应?
改造后的多线程异步请求代码
using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Threading; // 线程安全日志锁,避免多线程写入日志冲突 private static readonly object _logLock = new object(); public async Task ProcessMaterialsAsync() { // 1. 读取MaterialID列表(优化读取效率) var materialIds = new List<string>(); try { string[] lines = File.ReadAllLines(@"C:\Temp\Test.txt"); foreach (var line in lines) { string trimmedId = line.Trim(); if (!string.IsNullOrEmpty(trimmedId)) { materialIds.Add(trimmedId); UpdateLogTxtSafe($"Material_Count = {trimmedId}"); } } } catch (Exception ex) { UpdateLogTxtSafe($"读取ID文件失败:{ex.ToString()}"); return; } // 确保输出目录存在,避免写入文件时出错 string outputDir = @"C:\Temp\material"; if (!Directory.Exists(outputDir)) { Directory.CreateDirectory(outputDir); UpdateLogTxtSafe($"创建输出目录:{outputDir}"); } // 2. 复用HttpClient(线程安全,避免频繁创建导致套接字耗尽) string fcsEndPoint = @"https://www.api.co.za"; string fcsToken = textBoxDescription.Text.Trim(); using (var httpClient = new HttpClient()) { // 配置全局请求头,无需每次请求重复设置 httpClient.DefaultRequestHeaders.Accept.Clear(); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SessionToken", fcsToken); // 3. 异步并行处理所有MaterialID请求 var tasks = new List<Task>(); foreach (var id in materialIds) { tasks.Add(ProcessSingleMaterialAsync(id, httpClient, outputDir)); } // 等待所有请求完成 await Task.WhenAll(tasks); UpdateLogTxtSafe("所有MaterialID请求处理完成"); } } private async Task ProcessSingleMaterialAsync(string materialId, HttpClient httpClient, string outputDir) { UpdateLogTxtSafe($"提交请求 = {materialId}"); try { string uri = $"{fcsEndPoint}/material/pageIndex={materialId}"; // 异步发送POST请求(空内容直接用PostAsync,比PostAsJsonAsync更高效) using (var response = await httpClient.PostAsync(uri, null)) { if (response.IsSuccessStatusCode) { string responseStr = await response.Content.ReadAsStringAsync(); // 生成唯一文件名(时间戳精确到秒,避免重复) string fileName = Path.Combine(outputDir, $"Material_{materialId}_{DateTime.Now:yyyyMMddHHmmss}.txt"); // 异步写入文件 await File.WriteAllTextAsync(fileName, responseStr); UpdateLogTxtSafe($"请求成功,已写入文件:{fileName}"); } else if (response.StatusCode == System.Net.HttpStatusCode.Forbidden) { UpdateLogTxtSafe($"请求失败(禁止访问) = {materialId},状态码:{response.StatusCode}"); } else { UpdateLogTxtSafe($"请求失败 = {materialId},状态码:{response.StatusCode}"); } } } catch (HttpRequestException hEx) { UpdateLogTxtSafe($"HTTP请求异常 = {materialId}:{hEx.ToString()}"); } catch (Exception ex) { UpdateLogTxtSafe($"处理异常 = {materialId}:{ex.ToString()}"); } } // 线程安全的日志更新方法 private void UpdateLogTxtSafe(string logMessage) { lock (_logLock) { UpdateLogTxt(logMessage); } }
关键改造说明
- 复用HttpClient:原代码每次循环创建新HttpClient,会导致系统套接字耗尽。现在复用单个实例,它是线程安全的,能大幅提升请求效率。
- 异步并行处理:用
Task.WhenAll实现批量异步请求,相比同步循环,IO密集型场景下效率提升数倍,完美适配1000-5000个ID的批量处理需求。 - 线程安全日志:给日志写入操作加锁,避免多线程同时写入导致的日志内容混乱或异常。
- IO操作优化:用
File.ReadAllLines一次性读取ID文件,异步写入响应文件,进一步提升整体处理速度。 - 冗余代码清理:删除重复设置请求头的逻辑,简化URI拼接,清理不必要的变量声明,让代码更简洁。
- 前置检查:提前创建输出目录,避免因目录不存在导致的文件写入失败。
注意:如果是WinForms/WPF环境,需将触发方法(如按钮点击事件)标记为
async,避免阻塞UI线程:private async void btnStartProcess_Click(object sender, EventArgs e) { await ProcessMaterialsAsync(); }
内容的提问来源于stack exchange,提问作者M-Corp
相关产品推荐
相关产品推荐

