С#: Как я могу создать потоки IEnumerable <T>?
Скажем, у меня есть этот простой метод:
public IEnumerable<uint> GetNumbers()
{
uint n = 0;
while(n < 100)
yield return n++;
}
Как бы вы сделали эту тему безопасной? И под этим я подразумеваю, что вы получите этот перечислитель один раз и имеете несколько потоков, обрабатывающих все числа, без каких-либо дубликатов.
Я полагаю, что какой-то замок нужно использовать где-нибудь, но где должна быть блокировка для того, чтобы блок итератора был потокобезопасным? Что, в общем, нужно помнить, если вы хотите безопасный поток IEnumerable<T>
? Вернее, я предполагаю, что это будет безопасный поток IEnumerator<T>
...?
Ответы
Ответ 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();