c# - Parallel.ForEach 中的 C# List.Find() 有时返回 NULL
问题描述
使用以下代码,通常一切正常。但是,很少(可能每月一次,当代码每天运行时)该.Find()
方法找不到它正在寻找的项目。
List<string> names = new List<string>();
names.Add("bob");
names.Add("henry");
names.Add("mary");
List<Person> peopleList = GetListOfPeople();
Parallel.ForEach(names, (name) => {
Person personFound = peopleList.Find(p => p.Name.Equals(name));
int varThatCantBeNULL = personFound.Age;
});
偶尔peopleList.Find()
会返回一个对象,但它的字段为 NULL。我怎么知道这个?
- 重新运行应用程序不会再次出现问题(输入或状态不会改变会改变 peopleList 的内容)
- 添加代码以在问题发生时打印 peopleList 的内容表明列表中存在满足传递给的条件的项目
Find()
并且其字段不为 NULL
我无法重现问题,必须等待它有机/自然地发生。
我假设这与并行性和多个线程试图从同一个列表中读取有关。我已经通过捕获错误并重新尝试来解决这个问题,这似乎有效。
谁能提供有关为什么会发生这种情况的见解?我知道从多个线程写入一个公共变量是有问题的(竞争条件),但我不明白为什么从一个公共列表中读取会导致问题。
编辑
感谢您的回复。所有似乎都表明我的使用personFound
不是线程安全的,因为其他线程正在写入它(确实有代码写入我试图访问的字段并遇到问题)。
但是,这个线程中的这个答案Can I use local variables inside Parallel Foreach 循环(不会无意中重写以前的值) 表明我正在做的事情是线程安全的。好像有点矛盾。它确实说 ADO.NET 类不是线程安全的,我使用的是自定义类而不是 ADO.NET 类。
解决方案
既然您说您在阅读时从不写该列表 - 那么阅读本身不会引起任何问题。当只有读取器而根本没有写入器时,所有结构都是线程安全的,这就是不可变结构如此受欢迎的原因——您无法修改它们,因此它们在设计上是线程安全的。
但是,您正在修改Person
存储在列表中的条目字段,同时从其他线程读取这些字段。这当然不安全。
看来你相信
Person personFound = peopleList.Find(p => p.Name.Equals(name));
以某种方式创建一个副本,并且保证对字段的后续访问personFound
返回与您找到它时相同的值。不是这样的,如果Person
是引用类型(我们可以安全地假设它是)。
.NET 中有两种不同的“类型”:引用类型和值类型。引用类型的变量存储一个地址,通过该地址,您可以到达存储在其他地方的实际内容。
在您的情况下 -personFound
变量确实是“本地的”,但它只包含实际内容的地址,并且提到的内容被其他线程更改。所以当你这样做时:
int varThatCantBeNULL = personFound.Age;
它粗略的意思是 - 转到personFound
存储内容的位置并从那里抓取Age
。在此刻:
Person personFound = peopleList.Find(p => p.Name.Equals(name));
该 Age 可能不为空,但在当前线程有机会读取Age
下一行之前 - 另一个线程可能已经修改了它。
在您在编辑中链接的问题中 - 情况不一样,因为它们有
string strType = drow["type"]
虽然字符串也是引用类型 - 它是不可变的并且在创建后无法更改(“修改”字符串的方法只是创建一个副本,修改该副本并返回它)。因此,如果strType
其他线程无法更改内容。
推荐阅读
- flutter - 无法从方法返回类型值,因为它的返回类型为
- c++ - 在自定义目标运行后重建依赖目标
- javascript - React,映射到一个中等复杂的 json 对象以获取消息中的键
- python - 在 pyplot 中使用随机数据和位置创建颜色图
- activerecord - Rails 有没有办法禁止对所有 activerecord 对象执行任何操作(插入/销毁)
- c - 为什么我的程序编译但什么也不做?
- c# - EF Core 数据库迁移
- c++ - C ++在使用两个键盘时获取键盘适配器,确定按键来自哪里
- ebay-api - 在 ebay api 中获取产品的重量
- c++ - 声明 C++ 数组时,“array<>”和“[]”有什么区别?