C#:Как я могу сделать IEnumerable<T> потокобезопасным?
-
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 ();