WinForms多并发HttpWebRequest连接关闭问题及代码优化咨询
问题描述
开发WinForms应用时,同时向HTTPS API发送20-30个(无总数限制)HttpWebRequest请求,运行一段时间后随机出现错误:
error: The underlying connection was closed: An unexpected error occurred on a send
提供的代码片段如下:
任务启动代码
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; _tasks = new List<Task>(); cancelSources = new List<CancellationTokenSource>(); foreach (var emailList in smalChunks) { CancellationTokenSource cs = new CancellationTokenSource(); Task task = Task.Run(async () => { await HandleEmailList(emailList, userData, cs); }, cs.Token); _tasks.Add(task); cancelSources.Add(cs); } await Task.WhenAll(_tasks.ToArray()).ConfigureAwait(false);
请求处理函数
private async Task HandleEmailList(List<SorterResults> emailList, CancellationTokenSource cs) { try { var sortUrl = $"http://localhost:5000/api/xxx"; var httpWebRequest = (HttpWebRequest)WebRequest.Create(sortUrl); httpWebRequest.ContentType = "application/json"; httpWebRequest.Method = "POST"; using (var streamWriter = new StreamWriter(await httpWebRequest.GetRequestStreamAsync())) { var jsonData = JsonConvert.SerializeObject(emailList.ToArray()); var mailData = $"{{\"mails\":{jsonData}}}"; await streamWriter.WriteAsync(mailData); } APIResponseData responseResult; var httpResponse = (HttpWebResponse)(await httpWebRequest.GetResponseAsync()); using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) { var result = await streamReader.ReadToEndAsync(); responseResult = JsonConvert.DeserializeObject<APIResponseData>(result); } cs.Token.ThrowIfCancellationRequested(); } catch (Exception ex) { //ex.Message: The underlying connection was closed: An unexpected error occurred on a send } }
关键修改点
1. 替换HttpWebRequest为HttpClient(核心优化)
HttpWebRequest是老旧API,缺乏原生连接池优化,高并发下极易出现连接耗尽、随机断开的问题。HttpClient默认支持连接池复用,能大幅提升并发请求的稳定性和效率。
重构后的请求处理函数示例:
// 全局单例声明HttpClient(绝对不要每次请求新建) private static readonly HttpClient _httpClient = new HttpClient(); static YourFormClass() { // 配置TLS1.2及请求头 _httpClient.DefaultRequestHeaders.Accept.Clear(); _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // 调高连接池上限,适配高并发场景 ServicePointManager.DefaultConnectionLimit = 100; } private async Task HandleEmailList(List<SorterResults> emailList, CancellationToken token) { try { var sortUrl = "http://localhost:5000/api/xxx"; // 直接序列化List,无需转数组 var jsonData = JsonConvert.SerializeObject(emailList); var mailData = $"{{\"mails\":{jsonData}}}"; var content = new StringContent(mailData, Encoding.UTF8, "application/json"); // 所有异步操作传入取消令牌 using var response = await _httpClient.PostAsync(sortUrl, content, token); response.EnsureSuccessStatusCode(); // 主动触发HTTP错误异常 var result = await response.Content.ReadAsStringAsync(token); var responseResult = JsonConvert.DeserializeObject<APIResponseData>(result); } catch (OperationCanceledException) { // 处理请求取消逻辑 } catch (HttpRequestException ex) { // 处理HTTP相关异常 } catch (Exception ex) { // 其他异常处理 } }
2. 修正取消令牌的传递逻辑
原代码传递CancellationTokenSource给方法是冗余的,直接传递CancellationToken即可,同时要确保所有异步操作都传入令牌,避免资源泄漏。
修改任务启动代码:
_tasks = new List<Task>(); cancelSources = new List<CancellationTokenSource>(); foreach (var emailList in smalChunks) { var cs = new CancellationTokenSource(); var token = cs.Token; // 直接传递token,而非CancellationTokenSource var task = Task.Run(async () => await HandleEmailList(emailList, userData, token), token); _tasks.Add(task); cancelSources.Add(cs); } await Task.WhenAll(_tasks).ConfigureAwait(false);
3. 调高连接池默认上限
系统默认的ServicePointManager.DefaultConnectionLimit值为2,高并发下会导致连接排队超时,必须在应用启动时调高该值:
// 放在Program.cs或Form构造函数等启动入口 ServicePointManager.DefaultConnectionLimit = 100; // 根据实际并发量调整,建议50-200之间
4. 确保资源正确释放(若坚持用HttpWebRequest)
原代码中HttpWebResponse未用using包裹,会导致连接资源无法及时回收,必须修正:
// 给HttpWebResponse添加using包裹 using var httpResponse = (HttpWebResponse)(await httpWebRequest.GetResponseAsync()); using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) { var result = await streamReader.ReadToEndAsync(); responseResult = JsonConvert.DeserializeObject<APIResponseData>(result); }
内容的提问来源于stack exchange,提问作者J.Tran
相关产品推荐
相关产品推荐

