c# - Lock vs. ToArray for thread safe foreach access of List collection

标签 c# multithreading foreach locking toarray

我有一个 List 集合,我想在多线程应用程序中对其进行迭代。我每次迭代它时都需要保护它,因为它可能会被更改,而且我不希望在执行 foreach 时出现“集合已修改”异常。

正确的做法是什么?

  1. 每次访问或循环时都使用锁。我非常害怕死锁。也许我只是对使用 lock 偏执,不应该。如果我走这条路以避免死锁,我需要知道什么?锁是否相当有效?

  2. 每次执行 foreach 时,都使用 List<>.ToArray() 复制到一个数组。这会导致性能下降,但很容易做到。我担心内存抖动以及复制它的时间。只是显得过分。使用 ToArray 是线程安全的吗?

  3. 不要使用 foreach,而是使用 for 循环。每次执行此操作时我不需要进行长度检查以确保列表没有缩小吗?这看起来很烦人。

最佳答案

没有理由害怕死锁,它们很容易被发现。你的程序停止运行,死了。你真正应该害怕的是线程竞争,当你应该锁定时却没有锁定时你会遇到的那种错误。 非常难以诊断。

  1. 使用 lock 没问题,只要确保在接触该列表的任何代码中使用完全相同的锁定对象即可。就像从该列表中添加或删除项目的代码。如果该代码在迭代列表的同一线程上运行,那么您不需要锁。通常,这里发生死锁的唯一机会是,如果您有依赖于线程状态的代码,例如 Thread.Join(),同时它还持有该锁定对象。这应该很少见。

  2. 是的,迭代列表的副本始终是线程安全的,只要您在 ToArray() 方法周围使用锁。请注意,您仍然需要锁,没有结构改进。优点是您可以短时间持有锁,从而提高程序的并发性。缺点是它的 O(n) 存储要求,只有一个安全列表但不保护列表中的元素,以及总是有一个列表内容的陈旧 View 的棘手问题。尤其是最后一个问题,比较微妙,难以分析。如果您无法推断出副作用,那么您可能不应该考虑这个。

  3. 请务必将 foreach 检测种族的能力视为天赋,而不是问题。是的,显式的 for(;;) 循环不会抛出异常,它只会出现故障。就像重复相同的项目两次或完全跳过一个项目。您可以通过向后迭代来避免重新检查项目的数量。只要其他线程只调用 Add() 而不是 Remove(),其行为与 ToArray() 类似,您就会得到陈旧的 View 。并不是说这在实践中会起作用,索引列表也不是线程安全的。 List<> 将在必要时重新分配其内部数组。这不会以不可预测的方式工作和故障。

这里有两种观点。你可能会害怕并遵循常识,你会得到一个有效但可能不是最佳的程序。这是明智的,让老板高兴。或者您可以自己试验并找出歪曲规则会给您带来麻烦的方式。这会让你开心,你会成为一个更好的程序员。但是您的工作效率将会受到影响。我不知道你的日程安排是什么样的。

关于c# - Lock vs. ToArray for thread safe foreach access of List collection,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3128889/

相关文章:

php - 一段时间后 file_put_contents

c# - 如果为空则返回零的 Lambda 表达式

c# - 不包含采用 '1' 个参数的构造函数

c# - 将泛型类型与 asp.net 用户控件一起使用

android - 接收安卓 : Debounce calls to compute and return last computed value

c++ - 多线程 gtkmm 应用程序的最简单示例

c# - 使用 LINQ 扩展将对象添加到嵌套在字典中的列表

C# 在两个列表框之间进行相同的选择

java - 线程Join()方法输出?

razor - ASP.NET MVC 4 - for 循环发布模型集合属性,但 foreach 不发布