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

WinForms应用中单例HttpClient死锁问题的排查与解决

WinForms应用HttpClient单例改造后死锁问题排查与解决

我正在维护一个遗留Windows Forms应用,为了复用HttpClient将其改为单例模式,但这似乎引发了死锁问题,导致UI冻结无法恢复。

最初怀疑的相关代码

触发UI冻结的调用代码

private async void lbGroup_SelectedIndexChanged_1(object sender, EventArgs e)
{
    int groupId = this.lbGroup.SelectedIndex + 1;
    await LoadStores(groupId);

    // 以下代码导致应用冻结
    this.lbStore.DataSource = _stores;
    
    this.txtSearch.Enabled = true;
    this.lbStore.Enabled = true;
}

使用HttpClient的LoadStores方法

private async Task LoadStores(int group)
{
    try
    {
        HttpResponseMessage res = await _httpClient.GetAsync("api/GetStoresByGroup/" + group.ToString());
        res.EnsureSuccessStatusCode();
        if (res.IsSuccessStatusCode)
        {
            var serializedStores = await res.Content.ReadAsStringAsync();
            _stores = JsonConvert.DeserializeObject<IEnumerable<Store>>(serializedStores).Select(s => s.StoreName).ToList();
            res.Content.Dispose();
        }
    }
    catch (Exception ex)
    {
        ErrorLogger.LogError("Installation", $"Error getting stores list: {ex.Message}");
    }
}

HttpClient单例实现类

public static class HttpClientSingleton
{
    private static readonly HttpClient _instance;

    static HttpClientSingleton()
    {
        _instance = new HttpClient();
        _instance.BaseAddress = new Uri("https://www.i-city.co.za/");
        _instance.DefaultRequestHeaders.Accept.Clear();
        _instance.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
    }

    public static HttpClient Instance
    {
        get
        {
            return _instance;
        }
    }
}

窗体构造函数中的HttpClient初始化

public partial class frmInstallationHelper : Form
{
    private static string _configDir;
    private static string _localConfigDir;
    private static int _storeID;
    private static Activation _activation;
    private static HttpClient _httpClient = HttpClientSingleton.Instance;
    private static IEnumerable<string> _stores;
    private static IEnumerable<string> _franchisees;
    private int _smsCounter;

}

注:如果在LoadStores方法中用using包裹HTTP请求,应用运行正常,但这会销毁HttpClient,违背单例复用的初衷。


更新:问题定位与解决

经过排查,最初提供的代码并非死锁根源。真正的问题出在lbStore列表框的SelectedIndexChanged事件处理方法中:

原导致死锁的代码

private void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
    string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
    LoadFranchisees(store).Wait();
    this.lbFranchisees.DataSource = _franchisees;
}

修改后的解决代码

private async void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
     string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
     await LoadFranchisees(store);
     this.lbFranchisees.DataSource = _franchisees;
}

原因是在将所有.Wait()改为async/await时遗漏了这一处,Wait()会阻塞UI线程,进而引发死锁。

内容的提问来源于stack exchange,提问作者Mervyn Ludick

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 00:25:26