多线程场景下DataTable.ToList()抛出集合已修改异常的解决方案咨询
问题描述
我的应用通过多线程修改某一DataTable,已为添加行和提交数据库的操作实现独占锁机制(由两个不同线程处理)。现UI线程(第三个线程)需使用该表通过LINQ筛选数据并绑定到Chart控件。我已将操作拆分为最小指令以定位问题:
var s1 = dataTable.ToList(); //originally it was dataTable.ToEnumerable() var s2 = s1.Select(LINQ_expr1); var s3 = s2.Where(LINQ_expr2); var s4 = s3.OrderBy(LINQ_expr3);
异常始终发生在执行第一条指令时。目前我能想到的唯一可行方案是在此处加入锁机制,但希望避免该方案,寻求可创建变量副本且能避免此异常的替代方法。该表可能包含数十万行(会定期截断,但仅在分析完成后),因此希望找到比DataTable.Copy()开销更低的替代锁机制的方案。
可行方案
1. 只复制LINQ所需字段到POCO列表
直接放弃复制整个DataTable,手动遍历DataRow,只提取后续LINQ查询需要用到的字段,存入自定义的简单POCO类列表中。这种方式内存开销远低于复制整个DataTable,且单条DataRow的读取是线程安全的(只要写入线程只添加新行,不修改已有行):
// 定义匹配LINQ查询需求的POCO类 public class DataItem { public int Value { get; set; } public DateTime Timestamp { get; set; } // 仅保留需要的字段 } // UI线程读取数据 var s1 = new List<DataItem>(); foreach (DataRow row in dataTable.Rows) { // 仅复制必要字段,减少冗余数据 s1.Add(new DataItem { Value = row.Field<int>("Value"), Timestamp = row.Field<DateTime>("Timestamp") }); } // 后续LINQ操作基于这个精简列表 var s4 = s1.Select(LINQ_expr1).Where(LINQ_expr2).OrderBy(LINQ_expr3);
2. 短锁创建DataView视图
DataView是基于DataTable的轻量级视图,仅在创建DataView的瞬间加锁,锁的持有时间极短,对性能影响可以忽略。后续LINQ直接基于DataView的枚举操作,无需再锁:
IEnumerable<DataRowView> dataView; // 仅在创建DataView时加锁,避免长时间阻塞写入线程 lock (yourExistingLockObject) { dataView = new DataView(dataTable, "", "", DataViewRowState.CurrentRows).Cast<DataRowView>(); } // 后续LINQ操作基于这个视图枚举 var s4 = dataView.Select(LINQ_expr1).Where(LINQ_expr2).OrderBy(LINQ_expr3);
3. 生产者-消费者模式维护只读副本
让写入线程在添加新行的同时,把该行的必要数据同步到一个线程安全的只读集合(比如ConcurrentBag<T>)。UI线程直接查询这个集合,完全不需要锁,也不需要复制整个DataTable:
// 全局线程安全集合,仅由写入线程添加数据 private readonly ConcurrentBag<DataItem> _readOnlyData = new ConcurrentBag<DataItem>(); // 写入线程添加行时同步数据 lock (yourInsertLock) { var newRow = dataTable.NewRow(); // 填充行数据 newRow["Value"] = 123; newRow["Timestamp"] = DateTime.Now; dataTable.Rows.Add(newRow); // 同步必要字段到只读集合 _readOnlyData.Add(new DataItem { Value = (int)newRow["Value"], Timestamp = (DateTime)newRow["Timestamp"] }); } // UI线程直接查询只读集合,无锁开销 var s4 = _readOnlyData.Select(LINQ_expr1).Where(LINQ_expr2).OrderBy(LINQ_expr3);
内容的提问来源于stack exchange,提问作者misiek8v
相关产品推荐
相关产品推荐

