成员集合迭代线程安全

时间:2013-02-20 00:10:42

标签: c# multithreading thread-safety

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

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 )

3 个答案:

答案 0 :(得分:2)

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

因此,编译器可能会以这种方式生成代码,但它没有必要,因此您应该编写代码,就好像不编写代码一样。

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

答案 1 :(得分:1)

如果你不介意他们互相锁定,这可能是可行的。尽管如此,它在内存使用和对象创建方面会有点沉重。可能有一种更清洁的方式。

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

    private set {
        lock(_servers) {
            _servers = value;
        }
    }
}

答案 2 :(得分:0)

你应该像上面提到的那样使用锁,但是你也可以通过不迭代对列表的引用并使用.ToArray而不是迭代它来最小化锁被占用的时间。