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

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可运行,但同样不符合需求

问题分析

  1. 加载逻辑冗余:LoadList方法中,循环listShort时每次都清空并重新加载地点列表,导致地点列表被反复重置,可能在设置SelectedIndex时数据未稳定。
  2. 事件触发循环:设置SelectedIndex=0会触发SelectedIndexChanged事件,事件中调用的RefreshListAsstLocations()若重新加载listAsst内容,会再次触发SelectedIndexChanged,形成循环阻塞UI线程。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 14:07:11