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

C# WinForms串口事件调用HttpClient接口异常缓慢问题

串口事件中调用Web服务异常缓慢的原因及解决办法

问题场景

WinForms窗体的SerialPort_DataReceived事件(运行在独立线程)中调用Web服务接口耗时45-60秒,但同样的接口在Node.js中调用仅需约10秒,在窗体按钮点击事件中调用同一方法仅需11秒。

串口事件代码

private async void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    string indata = sp.ReadLine().Replace("?","").Replace(" ","");
    if(indata != null)
    {
        var weightMatch = Regex.Match(indata, @"(\d+\.\d+)kg$");
        var weight = weightMatch.Groups[1].Value;
        var message = barcode + "  Weight:" + weight;
        this.Invoke(new Action(() => { scanData.Add(new ScanDataItem(scanData.Count + 1, message, "")); }));
        this.Invoke(new Action(() => { mainList.SelectedIndex = mainList.Items.Count - 1; }));
        string jsonData = "";
        // 以下代码耗时比预期多30秒 - 内部调用Web服务
        var jsonString = await TrayWeighScan.doOrder(barcode, Decimal.Parse(weight));

        try
        {
            var jsonDoc = JsonDocument.Parse(jsonString);
            jsonData = JsonSerializer.Serialize(jsonDoc, new JsonSerializerOptions { WriteIndented = true });
        }
        catch (Exception)
        {
            jsonData = jsonString;
        }
        this.Invoke(new Action(() => { scanData.Last().responseData = jsonData; }));
        this.Invoke(new Action(() => { responseTextBox.Text = jsonData; }));
        this.Invoke(new Action(() => { statusTextBox.Text = "Ready to Scan"; }));
        this.Invoke(new Action(() => { busy = false; }));

    }
}

public class TrayWeighScan
{
    //private static readonly HttpClient httpClient = new();

    public static async Task<string> doOrder(string orderNumber, decimal weight ) 
    {
        HttpClient httpClient = new();
        var url = "http://r2hserver/logistics/weighscan?orderNumber="+orderNumber+"&weight="+weight.ToString("F2");
        HttpResponseMessage response = await httpClient.GetAsync(url);
        string responseText = await response.Content.ReadAsStringAsync();
        return responseText;
    }
}

按钮点击事件代码

private async void btnUrlTest_Click(object sender, EventArgs e)
{
    string orderNumber = "ORD100302338";
    decimal weight = 1.606m;
    //以下代码耗时11秒
    var a = await TrayWeighScan.doOrder(orderNumber,weight);
    var b = "";
 }

核心原因分析

  1. HttpClient频繁创建导致连接无法复用
    TrayWeighScan.doOrder方法每次调用都新建HttpClient实例,而HttpClient设计为可复用的单例对象。频繁创建实例会导致TCP连接无法复用,每次请求都要重新完成三次握手、TLS协商(如果是HTTPS)等流程,大幅增加请求耗时。按钮事件执行频率低,系统连接池资源充足,所以耗时不明显;但串口事件可能触发更频繁,连接池资源被快速耗尽,导致请求等待时间剧增。

  2. 多次UI线程切换的额外开销
    串口事件线程是IO线程池线程,代码中多次调用this.Invoke切换到UI线程执行更新操作,频繁的上下文切换会累积额外的性能开销,进一步拉长整体耗时。

解决办法

1. 复用HttpClient单例

修改TrayWeighScan类,使用静态单例的HttpClient,并添加ConfigureAwait(false)减少线程上下文切换:

public class TrayWeighScan
{
    // 静态单例HttpClient,复用TCP连接
    private static readonly HttpClient httpClient = new();

    public static async Task<string> doOrder(string orderNumber, decimal weight ) 
    {
        // 对URL参数进行转义,避免特殊字符导致请求异常
        var url = $"http://r2hserver/logistics/weighscan?orderNumber={Uri.EscapeDataString(orderNumber)}&weight={weight.ToString("F2")}";
        // ConfigureAwait(false) 避免await后回到原线程上下文,减少切换开销
        HttpResponseMessage response = await httpClient.GetAsync(url).ConfigureAwait(false);
        // 可选:确保响应成功,否则抛出异常便于排查问题
        response.EnsureSuccessStatusCode();
        string responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        return responseText;
    }
}

2. 合并UI线程更新操作

将多次Invoke调用合并为一次,减少上下文切换次数:

// 合并前的多次Invoke
// this.Invoke(new Action(() => { scanData.Add(...); }));
// this.Invoke(new Action(() => { mainList.SelectedIndex = ...; }));

// 合并后的单次Invoke
this.Invoke(new Action(() => {
    scanData.Add(new ScanDataItem(scanData.Count + 1, message, ""));
    mainList.SelectedIndex = mainList.Items.Count - 1;
}));

// ...

// 调用Web服务后的UI更新也合并
this.Invoke(new Action(() => {
    scanData.Last().responseData = jsonData;
    responseTextBox.Text = jsonData;
    statusTextBox.Text = "Ready to Scan";
    busy = false;
}));

验证效果

修改后,串口事件中调用Web服务的耗时会大幅降低,接近按钮事件和Node.js调用的耗时水平,主要原因是TCP连接复用消除了重复建立连接的开销,同时减少了UI线程切换的额外消耗。

内容的提问来源于stack exchange,提问作者Martin Thompson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 00:43:26