成员集合迭代线程安全性

本文关键字:安全性 线程 迭代 集合 成员 | 更新日期: 2023-09-27 18:00:16

我有一个后台线程正在执行类似于以下的操作:

class Client
{
    public ReadOnlyCollection<IPAddress> Servers { get; private set; }
    void UpdateServers( List<IPAddress> servers )
    {
        // this will be called on a background thread
        this.Servers = new ReadOnlyCollection( servers );
    }
}

在主线程上,使用者可能想要迭代Servers属性。

我知道使用foreach,迭代将是线程安全的,因为如果在迭代过程中调用UpdateServers,迭代器将属于旧实例。

使用for循环,可以使用以下使迭代安全

var serverList = client.Servers;
for ( int x = 0 ; x < serverList.Count ; ++x )
{
    DoSomething( serverList[ x ] );
    // ...
}

但我想知道的是,如果消费者决定使用进行迭代,是否有任何方法可以保证编译器会生成(或强制它生成,如果它还没有生成的话)上面的代码

for ( int x = 0 ; x < client.Servers.Count ; ++x )

成员集合迭代线程安全性

编译器会生成你告诉它的代码。可能会有一些优化来避免反复加载字段的值,但你真的不应该依赖这些。

因此,编译器可能会以这种方式生成代码,但它不必这样做,所以你应该像没有那样编写代码。

此外,像这样的事情可能非常棘手(例如,我认为您还应该使字段volatile)。最好的选择通常是使用锁,除非这些锁会给您带来真正的性能问题(这种情况很少发生)。

如果你不介意他们互相锁定,这可能是可行的。不过,就内存使用和对象创建而言,它会有点重。可能有一种更清洁的方法。

private ReadOnlyCollection<IPAddress> _servers;
public ReadOnlyCollection<IPAddress> Servers { 
    get {
        lock(_servers) {
            return new ReadOnlyCollection<IPAddress>(_servers);
        }
    }
    private set {
        lock(_servers) {
            _servers = value;
        }
    }
}

您应该像上面的人建议的那样使用锁,但您也可以通过不迭代对列表的引用,而是使用.ToArray,然后迭代来最小化持有锁的时间。