【发布时间】:2020-09-02 06:12:46
【问题描述】:
我有这段代码可以处理列表中的项目:
static readonly object _Lock = new object();
public class Item
{
public string Name;
public string ID;
}
static void Main(string[] args)
{
var items = new List<Item>
{
new Item { Name = "One", ID = "123" },
new Item { Name = "Two", ID = "234" },
new Item { Name = "Three", ID = "123" }
};
var itemsProcess = new ConcurrentBag<Item>();
Parallel.ForEach(items, (item) =>
{
Item itemProcess = null;
// lock (_Lock)
{
itemProcess = itemsProcess.FirstOrDefault(a => a.ID == item.ID);
}
if (itemProcess != null)
{
Console.WriteLine($"Item [{item.Name}] was already processed as [{itemProcess.Name}]");
}
else
{
itemsProcess.Add(item);
Console.WriteLine($"Processing item [{item.Name}]");
Thread.Sleep(1000); // do some work...
}
});
Console.ReadKey();
}
我基本上是使用ConcurrentBag 根据几个条件检查对象是否存在。
我期望总能得到类似的输出(顺序可能会有所不同):
Processing item [One]
Item [Three] was already processed as [One]
Processing item [Two]
但我有时会得到一个输出,这表明我的代码不是线程安全的:
Processing item [Three]
Processing item [One]
Processing item [Two]
所以我认为itemsProcess.FirstOrDefault() 会阻塞的假设是错误的。
使用lock 不会改变任何东西。显然,这里出了点问题,我真的不明白为什么?
我知道我可以通过其他方式“解决”这个问题(一种是在输入 Parallel.ForEach() 之前准备好列表),但我真的很想知道为什么会出现这种行为?
【问题讨论】:
-
Concurrentbag 已经是线程安全的,所以 lock 在这里不会改变任何东西。并且并行 foreach 循环也启动 3 个并行线程,因此每次 itemProcess 将为空,因此 else 部分将执行。如果我将 ConcurrentBag 更改为 List 并使用 lock 那么它对我有用,因为现在 lock 将被视为普通列表。
-
您的示例包含两个具有相同
ID的Item对象。这是故意的吗? -
@TheodorZoulias 是的,
ID可以复制,并且只需要处理具有给定ID的第一项。 -
@JohnathanBarclay 哦,我明白了。可能这个澄清应该是问题的一部分。
标签: c# concurrency parallel-processing parallel.foreach