You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 00:20:27