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

Является ли доходность возврата в С# потокобезопасным?

У меня есть следующий фрагмент кода:

private Dictionary<object, object> items = new Dictionary<object, object>;
public IEnumerable<object> Keys
{
    get
    {
        foreach (object key in items.Keys)
        {
            yield return key;
        }
    }
}

Является ли это потокобезопасным? Если нет, мне нужно поставить a lock вокруг цикла или yield return?

Вот что я имею в виду:

Thread1 обращается к свойству Keys, а Thread2 добавляет элемент в базовый словарь. На Thread1 влияет добавление Thread2?

4b9b3361

Ответ 1

Хорошо, я провел некоторое тестирование и получил интересный результат.

Похоже, что это больше проблема перечислителя базовой коллекции, чем ключевое слово yield. Перечислитель (на самом деле его метод MoveNext) генерирует (если выполняется правильно) a InvalidOperationException, потому что перечисление изменилось. Согласно документации MSDN метода MoveNext, это ожидаемое поведение.

Поскольку перечисление через коллекцию обычно не является потокобезопасным, yield return также не является.

Ответ 2

Что именно вы подразумеваете под потокобезопасностью?

Вы, конечно, не должны менять словарь во время его повтора, будь то в том же потоке или нет.

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

РЕДАКТИРОВАТЬ: Чтобы ответить на ваше редактирование, он никоим образом не соответствует коду блокировки. Нет блокировки, автоматически вынимаемой блоком итератора - и как бы он знал о syncRoot в любом случае?

Кроме того, просто блокировка возврата IEnumerable<TKey> не делает его потокобезопасным - потому что блокировка влияет только на период времени, когда она возвращает последовательность, а не период, в течение которого она повторяется.

Ответ 3

Отметьте это сообщение о том, что происходит за кулисами с ключевым словом yield:

За кулисами ключевого слова С# yield

Короче говоря, компилятор берет ключевое слово yield и генерирует целый класс в IL для поддержки функциональности. Вы можете проверить страницу после перехода и проверить код, который генерируется... и этот код выглядит так, будто он отслеживает идентификатор потока, чтобы все было в безопасности.

Ответ 4

Я считаю, что это так, но я не могу найти ссылку, подтверждающую ее. Каждый раз, когда любой поток вызывает foreach на итераторе, должен создаваться новый поток локального * экземпляра основного IEnumerator, поэтому не должно быть никакого "общего" состояния памяти, в котором могут возникать конфликты двух потоков...

  • Локальный поток - в том смысле, что эта ссылочная переменная привязана к кадру стека метода в этом потоке

Ответ 5

Я считаю, что реализация yield является потокобезопасной. В самом деле, вы можете запустить эту простую программу дома, и вы заметите, что состояние метода listInt() правильно сохраняется и восстанавливается для каждого потока без эффекта края из других потоков.

public class Test
{
    public void Display(int index)
    {
        foreach (int i in listInt())
        {
            Console.WriteLine("Thread {0} says: {1}", index, i);
            Thread.Sleep(1);
        }

    }

    public IEnumerable<int> listInt()
    {
        for (int i = 0; i < 5; i++)
        {
            yield return i;
        }
    }
}

class MainApp
{
    static void Main()
    {
        Test test = new Test();
        for (int i = 0; i < 4; i++)
        {
            int x = i;
            Thread t = new Thread(p => { test.Display(x); });
            t.Start();
        }

        // Wait for user
        Console.ReadKey();
    }
}

Ответ 6

class Program
{
    static SomeCollection _sc = new SomeCollection();

    static void Main(string[] args)
    {
        // Create one thread that adds entries and
        // one thread that reads them
        Thread t1 = new Thread(AddEntries);
        Thread t2 = new Thread(EnumEntries);

        t2.Start(_sc);
        t1.Start(_sc);
    }

    static void AddEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;

        for (int x = 0; x < 20; x++)
        {
            Trace.WriteLine("adding");
            sc.Add(x);
            Trace.WriteLine("added");
            Thread.Sleep(x * 3);
        }
    }

    static void EnumEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;
        for (int x = 0; x < 10; x++)
        {
            Trace.WriteLine("Loop" + x);
            foreach (int item in sc.AllValues)
            {
                Trace.Write(item + " ");
            }
            Thread.Sleep(30);
            Trace.WriteLine("");
        }
    }
}

class SomeCollection
{
    private List<int> _collection = new List<int>();
    private object _sync = new object();

    public void Add(int i)
    {
        lock(_sync)
        {
            _collection.Add(i);
        }
    }


    public IEnumerable<int> AllValues
    {
        get
        {
            lock (_sync)
            {
                foreach (int i in _collection)
                {
                    yield return i;
                }
            }
        }
    }
}