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

С#: Как я могу создать потоки IEnumerable <T>?

Скажем, у меня есть этот простой метод:

public IEnumerable<uint> GetNumbers()
{
    uint n = 0;
    while(n < 100)
        yield return n++;
}

Как бы вы сделали эту тему безопасной? И под этим я подразумеваю, что вы получите этот перечислитель один раз и имеете несколько потоков, обрабатывающих все числа, без каких-либо дубликатов.

Я полагаю, что какой-то замок нужно использовать где-нибудь, но где должна быть блокировка для того, чтобы блок итератора был потокобезопасным? Что, в общем, нужно помнить, если вы хотите безопасный поток IEnumerable<T>? Вернее, я предполагаю, что это будет безопасный поток IEnumerator<T>...?

4b9b3361

Ответ 1

В этом есть неотъемлемая проблема, потому что IEnumerator<T> имеет как MoveNext(), так и Current. Вам действительно нужен один вызов, например:

bool TryMoveNext(out T value)

в этот момент вы можете атомарно перейти к следующему элементу и получить значение. Реализация этого и все еще возможность использовать yield может быть сложной... Я подумаю об этом. Я думаю, вам нужно было бы обернуть итератор "non-threadsafe" в потокобезопасном, который с помощью атома выполнял MoveNext() и Current для реализации интерфейса, показанного выше. Я не знаю, как бы вы вернули этот интерфейс обратно в IEnumerator<T>, чтобы вы могли использовать его в foreach, хотя...

Если вы используете .NET 4.0, Parallel Extensions могут вам помочь - вам нужно будет объяснить больше о том, что вы пытаетесь сделать, хотя.

Это интересная тема - мне может понадобиться блог об этом...

EDIT: теперь я писал об этом с двумя подходами.

Ответ 2

Я предполагаю, что вам нужен Enumerator для сохранения потока, поэтому вы должны, вероятно, реализовать его.

Ответ 3

Я просто проверил этот бит кода:

static IEnumerable<int> getNums()
{
    Console.WriteLine("IENUM - ENTER");

    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine(i);
        yield return i;
    }

    Console.WriteLine("IENUM - EXIT");
}

static IEnumerable<int> getNums2()
{
    try
    {
        Console.WriteLine("IENUM - ENTER");

        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(i);
            yield return i;
        }
    }
    finally
    {
        Console.WriteLine("IENUM - EXIT");
    }
}

getNums2() всегда вызывает окончательную часть кода. Если вы хотите, чтобы IEnumerable был потокобезопасным, добавьте любые блокировки потоков, которые вы хотите, вместо писем, завяжите, используя ReaderWriterSlimLock, Семафор, Монитор и т.д.

Ответ 4

Ну, я не уверен, но, может быть, с некоторыми замками в вызывающем?

Проект:

Monitor.Enter(syncRoot);
foreach (var item in enumerable)
{
  Monitor.Exit(syncRoot);
  //Do something with item
  Monitor.Enter(syncRoot);
}
Monitor.Exit(syncRoot);

Ответ 5

Я думал, что вы не можете сделать ключевое слово yield потокобезопасным, если только вы не зависнете от уже потокобезопасного источника значений:

public interface IThreadSafeEnumerator<T>
{
    void Reset();
    bool TryMoveNext(out T value);
}

public class ThreadSafeUIntEnumerator : IThreadSafeEnumerator<uint>, IEnumerable<uint>
{
    readonly object sync = new object();

    uint n;

    #region IThreadSafeEnumerator<uint> Members
    public void Reset()
    {
        lock (sync)
        {
            n = 0;
        }
    }

    public bool TryMoveNext(out uint value)
    {
        bool success = false;

        lock (sync)
        {
            if (n < 100)
            {
                value = n++;
                success = true;
            }
            else
            {
                value = uint.MaxValue;
            }
        }

        return success;
    }
    #endregion
    #region IEnumerable<uint> Members
    public IEnumerator<uint> GetEnumerator()
    {
        //Reset(); // depends on what behaviour you want
        uint value;
        while (TryMoveNext(out value))
        {
            yield return value;
        }
    }
    #endregion
    #region IEnumerable Members
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        //Reset(); // depends on what behaviour you want
        uint value;
        while (TryMoveNext(out value))
        {
            yield return value;
        }
    }
    #endregion
}

Вам нужно будет решить, должно ли каждое типичное инициирование счетчика reset последовательности, или если клиентский код должен это сделать.

Ответ 6

Вы можете просто возвращать полную последовательность каждый раз, а не использовать выход:

return Enumerable.Range(0, 100).Cast<uint>().ToArray();