C# 返回IEnumerable线程是否安全?

C# 返回IEnumerable线程是否安全?,c#,.net-3.5,thread-safety,C#,.net 3.5,Thread Safety,我有一个VisualStudio2008C.NET3.5项目,我希望有一个线程安全的Foo对象池 public class FooPool { private object pool_lock_ = new object(); private Dictionary<int, Foo> foo_pool_ = new Dictionary<int, Foo>(); // ... public void Add(Foo f) {

我有一个VisualStudio2008C.NET3.5项目,我希望有一个线程安全的Foo对象池

public class FooPool
{
    private object pool_lock_ = new object();
    private Dictionary<int, Foo> foo_pool_ = new Dictionary<int, Foo>();

    // ...

    public void Add(Foo f)
    {
        lock (pool_lock_)
        {
            foo_pool_.Add(SomeFooDescriminator, f);
        }
    }

    public Foo this[string key]
    {
        get { return foo_pool_[key]; }
        set { lock (pool_lock_) { foo_pool_[key] = value; } }
    }

    public IEnumerable<Foo> Foos
    {
        get
        {
            lock (pool_lock_)
            {
                // is this thread-safe?
                return foo_pool_.Select(x => x.Value);
            }
        }
    }
}
公共IEnumerable Foos{get;}函数是线程安全的吗?或者,我需要克隆结果并返回新列表吗?

不需要

如果另一个线程在调用方枚举时添加到字典中,您将得到一个错误

相反,您可以执行以下操作:

lock (pool_lock_) {
    return foo_pool.Values.ToList();
}
它不是线程安全的。您需要返回ToList:

小心延期执行

事实上,实际代码是在锁退出后运行的

IEnumerable Foos{get;}函数是线程安全的吗

没有

或者,是否需要克隆结果并返回新列表

不,因为那也不对。给出错误答案的线程安全方法不是很有用

如果你锁定并复制了一个副本,那么你返回的东西就是过去的快照。释放锁时,集合可以更改为完全不同。如果你通过复制来保证线程安全,那么你现在就是在给来电者一包谎言


当您处理单线程代码时,一个合理的模型是,除非您采取特定措施来更改某个内容,否则一切都保持不变。在多线程代码中,这不是一个合理的模型。在多线程代码中,您应该假设相反的情况:一切都在不断变化,除非您采取特定的措施,例如锁定,以确保事情不会发生变化。分发一系列描述几百纳秒前遥远过去世界状态的食物有什么好处?整个世界在这段时间内可能会有所不同。

你可能想考虑一个同步集合,

SynchronizedCollection类 提供线程安全集合,该集合包含泛型参数指定为元素的类型的对象


如果您锁定每个读取访问,您将以非常糟糕的性能结束。在使用toList的建议中,您还将每次分配内存

如果您使用.NET4,只需使用新线程安全集合中的ConcurrentDictionary类。它们将为从多个线程访问数据提供非常快速的无锁机制


如果您使用的是旧的.NET版本,我建议您使用for cycle with count变量,而不是foreach。如果您只添加元素而不删除它们,那么它将起作用,如您的示例所示

。。。你是否建议锁不进入池本身,而是应该绕过任何使用池的代码,并且我试图使池本身线程安全是错误的?@PaulH:我建议你需要考虑两个问题:1这个池的用户实际上需要池提供什么服务?考虑到池的多线程性质,池实际上可以提供什么服务?多线程池通常不会列出池服务中的所有内容,因为1没有人需要它,2很难以安全、准确和高效的方式提供该服务。池通常提供从池中取出一些东西并将一些东西放回池中的服务。我非常感谢您的建议,我会给予更多的考虑。顺便说一句,“get{return foo_pool_ukey];}”已损坏。你忘了锁。因为字典和大多数.net类一样不是线程安全的。特别是它不允许人们同时读写。是的,但我能想到的最坏情况是Eric Lippert在下面为Foos函数概述的情况。这已经是该代码固有的弱点。是否存在比过时数据更糟糕的情况?i、 一个线程试图获取;而另一个做一套;,getter可能会获取旧数据,因为;线程还没有结束。@PaulH:如果作者在阅读过程中移动字典中的内容,你可能会得到错误的结果。您需要在读取周围设置一个锁,因为它不是原子的。
return foo_pool_.Select(x => x.Value).ToList();
// Don't do this
lock (pool_lock_)
{
    return foo_pool_.Select(x => x.Value); // This only prepares the statement, does not run it
}