WinForm中设置ListBox的SelectedIndex=0引发上下文切换死锁
上下文切换死锁:WinForm设置ListAssistant.SelectedIndex=0时崩溃
问题场景
现有WinForm包含两组配对ListBox:ListProvider→ListProviderLocations、ListAssistant→ListAssistantLocations,核心功能是保存医护人员/助理的地点选择偏好,用于其他表单的筛选逻辑。
在Form.Load()事件中需为两个ListBox设置默认选中第一项:
listProv.SelectedIndex = 0; listAsst.SelectedIndex = 0;
但执行listAsst.SelectedIndex = 0;时程序停滞,最终抛出上下文切换死锁错误;注释该行或设置SelectedIndex=1可正常运行,但不符合需求。
核心代码
ListBox加载逻辑
两个ListBox的填充逻辑一致,仅引用对象不同:
private void LoadListProviderLocations () { List<Provider> listShort = Providers.GetAll(); this.listProv.Items.Clear(); for (int i = 0; i < listShort.Count; i++) { if (listShort[i].IsHidden == false && listShort[i].AnesthProvType == 1) { listProv.Items.Add(Lan.g(this, listShort[i].GetLongDesc())); } this.listProvLocation.Items.Clear(); provLocationCur = AnesthLocations.Refresh(); for (int j = 0; j < provLocationCur.Count; j++) { if (provLocationCur[j].Hidden == false) { listProvLocation.Items.Add(provLocationCur[j].LocationName); } } } } private void LoadListAsstLocations () { List<Provider> listShort = Providers.GetAll(); this.listAsst.Items.Clear(); for (int i = 0; i < listShort.Count; i++) { if (listShort[i].IsHidden == false && listShort[i].AnesthProvType == 2) { listAsst.Items.Add(Lan.g(this, listShort[i].GetLongDesc())); } this.listAsstLocation.Items.Clear(); asstLocationCur = AnesthLocations.Refresh(); for (int j = 0; j < asstLocationCur.Count; j++) { if (asstLocationCur[j].Hidden == false) { listAsstLocation.Items.Add(asstLocationCur[j].LocationName); } } } }
ListBox设计器配置
// // listProv // this.listProv.Location = new System.Drawing.Point(41, 193); this.listProv.Name = "listProv"; this.listProv.Size = new System.Drawing.Size(242, 173); this.listProv.TabIndex = 4; this.listProv.SelectedIndexChanged += new System.EventHandler(this.listProv_SelectedIndexChanged); // // listAsst // this.listAsst.FormattingEnabled = true; this.listAsst.Location = new System.Drawing.Point(19, 343); this.listAsst.Name = "listAsst"; this.listAsst.Size = new System.Drawing.Size(242, 199); this.listAsst.TabIndex = 13; this.listAsst.SelectedIndexChanged += new System.EventHandler(this.listAsst_SelectedIndexChanged);
listAsst的SelectedIndexChanged事件
private void listAsst_SelectedIndexChanged (object sender, EventArgs e) { try { provNum = AnesthLocations.GetAnesthProviderNum(listAsst.SelectedItem.ToString()); } catch { } listAnesthProvider = AnesthProviders.Refresh(); for (int i = 0; i < listAnesthProvider.Count; i++) { if (listAnesthProvider[i].ProviderNum == provNum) { long locationNum = listAnesthProvider[i].LocationNum; asstLocationName = AnesthLocations.GetLocationName(locationNum); foreach (var item in asstLocationCur){ for (int j = 0; j < listAsstLocation.Items.Count; j++) { if (asstLocationName == item.LocationName) { listAsstLocation.SetSelected(j, true); } } } } } RefreshListAsstLocations(); }
已尝试方案
- 设置
listAsst.SelectedIndex=1可正常运行,但不符合默认选中第一项的需求 - 注释
listAsst.SelectedIndex=0可运行,但同样不符合需求
问题分析
- 加载逻辑冗余:LoadList方法中,循环
listShort时每次都清空并重新加载地点列表,导致地点列表被反复重置,可能在设置SelectedIndex时数据未稳定。 - 事件触发循环:设置
SelectedIndex=0会触发SelectedIndexChanged事件,事件中调用的RefreshListAsstLocations()若重新加载listAsst内容,会再次触发SelectedIndexChanged,形成循环阻塞UI线程。 - UI线程阻塞:事件中的数据库查询(如
AnesthProviders.Refresh())若为同步操作,会占用UI线程,导致上下文切换死锁。
解决方案
1. 优化ListBox加载逻辑
将地点列表加载移出循环,避免重复操作:
private void LoadListProviderLocations () { List<Provider> listShort = Providers.GetAll(); this.listProv.Items.Clear(); // 仅加载一次地点列表 this.listProvLocation.Items.Clear(); provLocationCur = AnesthLocations.Refresh(); foreach (var loc in provLocationCur) { if (!loc.Hidden) { listProvLocation.Items.Add(loc.LocationName); } } // 加载医护人员列表 foreach (var prov in listShort) { if (!prov.IsHidden && prov.AnesthProvType == 1) { listProv.Items.Add(Lan.g(this, prov.GetLongDesc())); } } } private void LoadListAsstLocations () { List<Provider> listShort = Providers.GetAll(); this.listAsst.Items.Clear(); // 仅加载一次地点列表 this.listAsstLocation.Items.Clear(); asstLocationCur = AnesthLocations.Refresh(); foreach (var loc in asstLocationCur) { if (!loc.Hidden) { listAsstLocation.Items.Add(loc.LocationName); } } // 加载助理列表 foreach (var prov in listShort) { if (!prov.IsHidden && prov.AnesthProvType == 2) { listAsst.Items.Add(Lan.g(this, prov.GetLongDesc())); } } }
2. 避免设置SelectedIndex时触发循环事件
在Form.Load中先解绑事件,设置完成后再手动执行事件逻辑并重新绑定:
private void Form_Load(object sender, EventArgs e) { // 先加载所有列表数据 LoadListProviderLocations(); LoadListAsstLocations(); // 临时解绑事件,避免设置选中项时触发循环 listProv.SelectedIndexChanged -= listProv_SelectedIndexChanged; listAsst.SelectedIndexChanged -= listAsst_SelectedIndexChanged; // 设置默认选中项(需判断列表是否有数据) if (listProv.Items.Count > 0) listProv.SelectedIndex = 0; if (listAsst.Items.Count > 0) listAsst.SelectedIndex = 0; // 手动执行选中后的逻辑 if (listProv.SelectedIndex != -1) listProv_SelectedIndexChanged(listProv, EventArgs.Empty); if (listAsst.SelectedIndex != -1) listAsst_SelectedIndexChanged(listAsst, EventArgs.Empty); // 重新绑定事件 listProv.SelectedIndexChanged += listProv_SelectedIndexChanged; listAsst.SelectedIndexChanged += listAsst_SelectedIndexChanged; }
3. 优化SelectedIndexChanged事件逻辑
简化查找逻辑,避免重复操作,并处理RefreshListAsstLocations()的事件冲突:
private void listAsst_SelectedIndexChanged (object sender, EventArgs e) { if (listAsst.SelectedItem == null) return; long provNum = 0; try { provNum = AnesthLocations.GetAnesthProviderNum(listAsst.SelectedItem.ToString()); } catch { // 可添加日志记录异常 return; } listAnesthProvider = AnesthProviders.Refresh(); listAsstLocation.ClearSelected(); bool found = false; foreach (var anesthProv in listAnesthProvider) { if (anesthProv.ProviderNum == provNum) { asstLocationName = AnesthLocations.GetLocationName(anesthProv.LocationNum); // 简化地点查找逻辑 int targetIndex = listAsstLocation.Items.IndexOf(asstLocationName); if (targetIndex != -1) { listAsstLocation.SetSelected(targetIndex, true); found = true; break; } } } // 若RefreshListAsstLocations会修改listAsst,需临时解绑事件避免循环 listAsst.SelectedIndexChanged -= listAsst_SelectedIndexChanged; RefreshListAsstLocations(); listAsst.SelectedIndexChanged += listAsst_SelectedIndexChanged; }
4. 排查UI线程阻塞
若仍存在死锁,需检查AnesthLocations.GetAnesthProviderNum、AnesthProviders.Refresh等方法是否包含同步数据库操作,建议改为异步操作(如async/await)避免阻塞UI线程。
内容的提问来源于stack exchange,提问作者William J. Starck
相关产品推荐
相关产品推荐

