【问题标题】:How do I freeze a collection so that I can iterate through it?如何冻结集合以便可以迭代它?
【发布时间】:2018-06-06 12:32:24
【问题描述】:

以下代码给了我一个例外:

var snapshot = BluetoothCapture.Instance.Snapshot();
var allowedDevice = snapshot.FirstOrDefault( _ => some_expression );

集合已修改;枚举操作可能无法执行。

我想我可以使用 lock 来冻结集合,以便我可以遍历它。但是,我仍然遇到同样的异常。

下面的类定义有一个 Snapshot 方法尝试这样做:

public partial class BluetoothCapture
{
    ...

    public void Capture()
    {
        _watcher = DeviceInformation.CreateWatcher();
        _watcher.Added += (s, e) => { _devices.Add(e); };
        _watcher.Start();
    }

    public IEnumerable<DeviceInformation> Snapshot()
    {
        lock (_devices)
        {
            return _devices.AsReadOnly();
        }
    }
}

有什么建议吗?

【问题讨论】:

  • _devices的类型是什么?
  • 你读过lock吗?它与此无关
  • _devices.Add(e);至少这个也应该被锁定
  • 请注意,lock 只是 每个人 必须同意使用的锁。这就像竖起一道门并说“每个想要进入该物业的人都必须通过大门,而我们在任何特定时间只允许一个人进入该物业”。换句话说,门口会有一个队列,等待该物业上的任何人退出。但是,锁/大门并不能阻止其他人完全无视大门。您想要等待的所有代码也必须使用锁,否则您最终会得到您现在看到的内容。

标签: c# locking


【解决方案1】:

当您需要停止一个代码块以在多个线程中执行时使用锁定(停止并行执行)。 如果 Capture 被多次调用,那么,是的,您可以在前一次完成之前调用一次写入。

您可以使用 ConcurrentBag。 ConcurrentBag 是一个类似对象的列表,但它是线程安全的(通用列表都不是)。 但是,ConcurrentBag 是无序集合,所以它不保证有序。

如果您需要一些有序列表,可以查看此链接 Thread safe collections in .NET

你也可以在Add中做一个“锁”(不是在get中)

 _watcher.Added += (s, e) => { lock(_devices){_devices.Add(e); }};

但是,如果您的应用运行了一段时间,您可能会遇到内存和性能问题(添加不会异步),即使 Capture 是。

【讨论】:

    【解决方案2】:

    lock 确实是非常有用的概念,但只有当我们明智地使用它时。

    如果在进一步的代码中,您不想更新snapshot 的引用(您从BluetoothCapture.Instance.Snapshot() 获得的集合),而只需执行一些Linq 查询以获取过滤值以执行一些逻辑。 你可以避免使用lock

    这也是有益的,因为不做lock 你实际上并没有持有其他线程来执行它的逻辑。 - 我们不应该忽视这样一个事实,即糟糕地使用 lock 也会导致像 dead-lock 这样的严重问题。

    你得到这个异常,很可能是你正在执行linq查询的集合;正在由其他线程更新。 (我也有这个问题)。

    您可以做一件事,而不是使用集合的一般引用(您从BluetoothCapture.Instance.Snapshot() 获得的那个),您可以创建一个本地列表 - 因为它是本地的,它不会被其他线程更新。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-31
      • 2020-12-21
      • 1970-01-01
      • 2021-08-09
      • 2015-10-09
      • 2014-05-04
      • 2019-02-14
      • 1970-01-01
      相关资源
      最近更新 更多