.NET Maui中移除持续更新集合项避免索引越界错误
问题分析与解决思路
你的核心问题是多线程并发操作ObservableCollection导致的索引越界:Binance Socket的回调在后台线程遍历并更新集合,而RemoveIndicator在UI线程修改集合结构,两者无同步机制,导致遍历过程中集合元素被移除,索引失效触发错误。以下是具体的解决方案:
方案一:线程同步+集合快照
关键改进点
- 使用统一锁对象同步所有集合操作,确保同一时间只有一个线程访问集合。
- 遍历前创建集合快照(
ToList()),避免直接遍历原集合时被修改。 - 所有UI相关操作(集合结构修改、元素属性更新)切换到UI线程执行,符合WPF/MAUI等框架的线程模型。
修改后的代码示例
private readonly object _collectionLock = new object(); private readonly Dispatcher _dispatcher = Application.Current.Dispatcher; public MainViewModel() { UpdateAllCommand = new Command(async () => await UpdateAll()); RemoveIndicatorCommand = new Command(RemoveIndicator); } async Task UpdateAll() { var result = await socketClient.UsdFuturesApi.SubscribeToMiniTickerUpdatesAsync(Symbol, async data => { if (Indicators == null) return; // 加锁获取集合快照,避免遍历过程中原集合被修改 List<Indicator> indicatorsSnapshot; lock (_collectionLock) { indicatorsSnapshot = Indicators.ToList(); } foreach (var indicator in indicatorsSnapshot) { try { // 耗时操作放在锁外,避免阻塞UI线程 var values = await GetIndicator(indicator.Name, indicator.Period); // 切换到UI线程更新属性,同时检查元素是否仍在集合中 await _dispatcher.InvokeAsync(() => { lock (_collectionLock) { if (Indicators.Contains(indicator)) { indicator.Value = values[^1]; } } }); } catch (Exception ex) { // 异常处理逻辑 } } }); } void RemoveIndicator() { // 集合结构修改必须在UI线程执行,先调度再锁 _dispatcher.Invoke(() => { lock (_collectionLock) { if (Indicators.Count > 0) { Indicators.RemoveAt(Indicators.Count - 1); } } }); }
方案二:避免遍历原集合(简化版)
如果不想用锁,可以直接遍历集合快照,同时在更新属性前检查元素是否仍在原集合中:
async Task UpdateAll() { var result = await socketClient.UsdFuturesApi.SubscribeToMiniTickerUpdatesAsync(Symbol, async data => { if (Indicators == null || Indicators.Count == 0) return; // 创建快照,遍历快照而非原集合 var snapshot = Indicators.ToList(); foreach (var indicator in snapshot) { try { var values = await GetIndicator(indicator.Name, indicator.Period); // 切换到UI线程,检查元素是否存在后更新 await _dispatcher.InvokeAsync(() => { if (Indicators.Contains(indicator)) { indicator.Value = values[^1]; } }); } catch (Exception ex) { // 异常处理逻辑 } } }); } void RemoveIndicator() { // 集合修改必须在UI线程 _dispatcher.Invoke(() => { if (Indicators.Count > 0) { Indicators.RemoveAt(Indicators.Count - 1); } }); }
为什么你之前的lock没生效?
大概率是锁的范围不对:
- 你可能只在遍历的部分代码加了锁,没有覆盖
RemoveIndicator的集合修改操作。 - 或者使用了不同的锁对象,导致线程同步失效。
内容的提问来源于stack exchange,提问作者Andrei Popa
相关产品推荐
相关产品推荐

