锁内返回Linq.Where生成的IEnumerable是否线程安全?调用ToList能否实现?
代码示例
public class Data { public bool Init { get; set; } public string Value {get; set; } } public class Example { private Object myObject = new Object(); private List<Data> myList = new List<Data> { new Data { Init = true, Value = "abc" }, new Data { Init = false, Value = "def" }, }; public IEnumerable<string> Get() { lock(this.myObject) { return this.myList.Where(i => i.Init == true).Select(i => i.Value); } } public void Set(string value) { lock (this.myObject) { this.myList.Add(new Data { Init = false, Value = value }); } } }
问题解答
问题1:当多个线程调用Get()方法时,该方法是否具备线程安全性?
不具备线程安全性。原因是Get()返回的是Linq延迟执行的IEnumerable<string>,锁仅在创建查询表达式的短暂时间内生效。锁释放后,其他线程(比如调用Set()的线程)可以修改myList。当调用方遍历这个返回的枚举器时,myList可能正在被修改,这会直接抛出InvalidOperationException,或者产生不可预期的遍历结果。
问题2:在上述Linq查询末尾调用.ToList(),能否让Get()方法实现线程安全?
可以实现Get()方法针对myList访问的线程安全。因为.ToList()会在锁的保护下立即完成对myList的遍历,把符合条件的元素复制到一个独立的新List<string>中。锁释放后,即使其他线程修改原myList,也不会影响已经返回的这个副本。调用方遍历返回的列表时,操作的是与原myList无关的独立集合,不会出现并发修改的问题。
需要注意:这种线程安全仅针对myList的集合访问和复制。如果Data对象的Init或Value属性在其他线程被修改,依然会存在线程安全风险——但当前代码中Set()方法只添加新的Data对象,并未修改已有对象的属性,因此在当前场景下是安全的。
内容的提问来源于stack exchange,提问作者NirMH
相关产品推荐
相关产品推荐

