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
相关产品推荐
相关产品推荐

