C#:Как я могу сделать IEnumerable<T> потокобезопасным?

StackOverflow https://stackoverflow.com/questions/1605745

  •  05-07-2019
  •  | 
  •  

Вопрос

Допустим, у меня есть такой простой метод:

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

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

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

Это было полезно?

Решение

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

bool TryMoveNext(out T value)

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

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

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

РЕДАКТИРОВАТЬ: теперь я веду блог об этом с двумя подходами .

Другие советы

Я только что проверил этот фрагмент кода:

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, Semaphore, Monitor и т. Д.

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

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

Сквозняк:

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

Я думал, что вы не можете сделать ключевое слово 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
}

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top