Подтвердить что ты не робот

Возвращает IEnumerable <> потокобезопасный?

У меня есть проект Visual Studio 2008 С#.NET 3.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);
            }
        }
    }
}

Является ли функция public IEnumerable<Foo> Foos { get; } безопасной для потоков? Или мне нужно клонировать результат и возвращать новый список?

4b9b3361

Ответ 1

Нет, это не так.

Если другой поток добавляет словарь, пока ваш абонент перечисляет его, вы получите сообщение об ошибке.

Вместо этого вы можете:

lock (pool_lock_) {
    return foo_pool.Values.ToList();
}

Ответ 2

Является ли функция IEnumerable<Foo> Foos { get; } безопасной для потоков?

Нет.

Или мне нужно клонировать результат и возвращать новый список?

Нет, потому что это тоже не так. Метод потокобезопасности, который дает неправильный ответ, не очень полезен.

Если вы заблокируете и сделаете копию, то вещь, которую вы возвращаете, является снимком прошлого. Коллекция может быть изменена, чтобы быть полностью другой момент освобождения замка. Если вы сделаете это потокобезопасным путем создания копии, то теперь вы передаете сумку, полную лжи, вашему вызывающему.

Когда вы имеете дело с однопоточным кодом, разумная модель заключается в том, что все остается неизменным, если вы не примете конкретные меры для изменения вещи. Это не разумная модель в многопоточном коде. В многопоточном коде вы должны принять обратное: все постоянно меняется, если вы не принимаете конкретные меры (например, блокировку), чтобы гарантировать, что вещи не меняются. Какая польза от раздачи последовательности Фосов, которые описывают состояние мира в далеком прошлом, сотни наносекунд назад? Весь мир может отличаться в течение этого времени.

Ответ 3

Он не является потокобезопасным. Вам нужно вернуть ToList():

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
}

Ответ 4

Возможно, вы захотите рассмотреть SynchronizedCollection,

Класс SynchronizedCollection Предоставляет потокобезопасную коллекцию, содержащую объекты типа, заданные общим параметром как элементы.

http://msdn.microsoft.com/en-us/library/ms668265.aspx

Ответ 5

Если вы заблокируете доступ к чтению, вы закончите очень плохую производительность. И в предложениях использовать toList вы также будете выделять память каждый раз.

Если вы используете .NET 4, просто используйте класс ConcurrentDictionary из новых коллекций с потоковой безопасностью. Они будут обеспечивать очень быстрые (блокирующие) механизмы для доступа к данным из нескольких потоков.

http://msdn.microsoft.com/en-us/library/dd997305.aspx

Если вы используете старую версию .NET, я бы предложил вам использовать для цикла с переменной count вместо foreach, она будет работать, если вы добавите только элементы без их удаления (как в вашем примере)