如何冻结集合以便我可以遍历它?

时间:2018-06-06 12:32:24

标签: c# locking

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

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();
        }
    }
}

有什么建议吗?

2 个答案:

答案 0 :(得分:0)

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

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

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

你也可以做一个&#34;锁定&#34;在添加(不在获取)

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

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

答案 1 :(得分:0)

lock是非常有用的概念,但只有在我们明智地使用它时才会这样。

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

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

您正在获得此异常,最有可能的是您正在执行linq查询的集合;正在由其他一些线程更新。 (我也遇到了这个问题)。

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